From 6cd7e0fe445ba5475470404c29972c657d7db06a Mon Sep 17 00:00:00 2001 From: MariuszC Date: Sat, 18 Jan 2020 20:01:00 +0100 Subject: [PATCH] Python3 Migrate --- .idea/inspectionProfiles/Project_Default.xml | 22 + .idea/misc.xml | 7 +- .idea/mopidy-radionet.iml | 9 +- .idea/workspace.xml | 692 +- mopidy_radionet/__init__.py | 2 +- mopidy_radionet/radionet.py | 6 +- setup.py | 11 +- venv/bin/activate | 76 + venv/bin/activate.csh | 37 + venv/bin/activate.fish | 75 + venv/bin/chardetect | 10 + venv/bin/easy_install | 12 + venv/bin/easy_install-3.7 | 12 + venv/bin/mopidy | 10 + venv/bin/pip | 12 + venv/bin/pip3 | 12 + venv/bin/pip3.7 | 12 + venv/bin/python | 1 + venv/bin/python3 | 1 + venv/bin/python3.7 | 1 + .../Mopidy-3.0.1.dist-info/INSTALLER | 1 + .../Mopidy-3.0.1.dist-info/LICENSE | 202 + .../Mopidy-3.0.1.dist-info/METADATA | 159 + .../Mopidy-3.0.1.dist-info/RECORD | 153 + .../Mopidy-3.0.1.dist-info/WHEEL | 5 + .../Mopidy-3.0.1.dist-info/entry_points.txt | 10 + .../Mopidy-3.0.1.dist-info/top_level.txt | 1 + .../Pykka-2.0.2.dist-info/INSTALLER | 1 + .../Pykka-2.0.2.dist-info/LICENSE | 202 + .../Pykka-2.0.2.dist-info/METADATA | 89 + .../Pykka-2.0.2.dist-info/RECORD | 36 + .../site-packages/Pykka-2.0.2.dist-info/WHEEL | 6 + .../Pykka-2.0.2.dist-info/top_level.txt | 1 + .../DESCRIPTION.rst | 50 + .../certifi-2019.11.28.dist-info/INSTALLER | 1 + .../certifi-2019.11.28.dist-info/METADATA | 74 + .../certifi-2019.11.28.dist-info/RECORD | 14 + .../certifi-2019.11.28.dist-info/WHEEL | 6 + .../metadata.json | 1 + .../top_level.txt | 1 + .../site-packages/certifi/__init__.py | 3 + .../site-packages/certifi/__main__.py | 2 + .../site-packages/certifi/cacert.pem | 4602 +++++++++ .../python3.7/site-packages/certifi/core.py | 15 + .../chardet-3.0.4.dist-info/DESCRIPTION.rst | 70 + .../chardet-3.0.4.dist-info/INSTALLER | 1 + .../chardet-3.0.4.dist-info/METADATA | 96 + .../chardet-3.0.4.dist-info/RECORD | 91 + .../chardet-3.0.4.dist-info/WHEEL | 6 + .../chardet-3.0.4.dist-info/entry_points.txt | 3 + .../chardet-3.0.4.dist-info/metadata.json | 1 + .../chardet-3.0.4.dist-info/top_level.txt | 1 + .../site-packages/chardet/__init__.py | 39 + .../site-packages/chardet/big5freq.py | 386 + .../site-packages/chardet/big5prober.py | 47 + .../site-packages/chardet/chardistribution.py | 233 + .../chardet/charsetgroupprober.py | 106 + .../site-packages/chardet/charsetprober.py | 145 + .../site-packages/chardet/cli/__init__.py | 1 + .../site-packages/chardet/cli/chardetect.py | 85 + .../chardet/codingstatemachine.py | 88 + .../python3.7/site-packages/chardet/compat.py | 34 + .../site-packages/chardet/cp949prober.py | 49 + .../python3.7/site-packages/chardet/enums.py | 76 + .../site-packages/chardet/escprober.py | 101 + .../python3.7/site-packages/chardet/escsm.py | 246 + .../site-packages/chardet/eucjpprober.py | 92 + .../site-packages/chardet/euckrfreq.py | 195 + .../site-packages/chardet/euckrprober.py | 47 + .../site-packages/chardet/euctwfreq.py | 387 + .../site-packages/chardet/euctwprober.py | 46 + .../site-packages/chardet/gb2312freq.py | 283 + .../site-packages/chardet/gb2312prober.py | 46 + .../site-packages/chardet/hebrewprober.py | 292 + .../site-packages/chardet/jisfreq.py | 325 + .../python3.7/site-packages/chardet/jpcntx.py | 233 + .../chardet/langbulgarianmodel.py | 228 + .../chardet/langcyrillicmodel.py | 333 + .../site-packages/chardet/langgreekmodel.py | 225 + .../site-packages/chardet/langhebrewmodel.py | 200 + .../chardet/langhungarianmodel.py | 225 + .../site-packages/chardet/langthaimodel.py | 199 + .../site-packages/chardet/langturkishmodel.py | 193 + .../site-packages/chardet/latin1prober.py | 145 + .../site-packages/chardet/mbcharsetprober.py | 91 + .../site-packages/chardet/mbcsgroupprober.py | 54 + .../python3.7/site-packages/chardet/mbcssm.py | 572 ++ .../site-packages/chardet/sbcharsetprober.py | 132 + .../site-packages/chardet/sbcsgroupprober.py | 73 + .../site-packages/chardet/sjisprober.py | 92 + .../chardet/universaldetector.py | 286 + .../site-packages/chardet/utf8prober.py | 82 + .../site-packages/chardet/version.py | 9 + .../python3.7/site-packages/easy-install.pth | 2 + .../idna-2.8.dist-info/INSTALLER | 1 + .../idna-2.8.dist-info/LICENSE.rst | 80 + .../site-packages/idna-2.8.dist-info/METADATA | 239 + .../site-packages/idna-2.8.dist-info/RECORD | 22 + .../site-packages/idna-2.8.dist-info/WHEEL | 6 + .../idna-2.8.dist-info/top_level.txt | 1 + .../python3.7/site-packages/idna/__init__.py | 2 + .../lib/python3.7/site-packages/idna/codec.py | 118 + .../python3.7/site-packages/idna/compat.py | 12 + venv/lib/python3.7/site-packages/idna/core.py | 396 + .../python3.7/site-packages/idna/idnadata.py | 1979 ++++ .../python3.7/site-packages/idna/intranges.py | 53 + .../site-packages/idna/package_data.py | 2 + .../python3.7/site-packages/idna/uts46data.py | 8205 +++++++++++++++++ .../site-packages/mopidy/__init__.py | 15 + .../site-packages/mopidy/__main__.py | 231 + .../site-packages/mopidy/audio/__init__.py | 10 + .../site-packages/mopidy/audio/actor.py | 855 ++ .../site-packages/mopidy/audio/constants.py | 14 + .../site-packages/mopidy/audio/listener.py | 94 + .../site-packages/mopidy/audio/scan.py | 302 + .../site-packages/mopidy/audio/tags.py | 161 + .../site-packages/mopidy/audio/utils.py | 100 + .../python3.7/site-packages/mopidy/backend.py | 445 + .../site-packages/mopidy/commands.py | 489 + .../site-packages/mopidy/config/__init__.py | 321 + .../site-packages/mopidy/config/default.conf | 25 + .../site-packages/mopidy/config/keyring.py | 177 + .../site-packages/mopidy/config/schemas.py | 125 + .../site-packages/mopidy/config/types.py | 323 + .../site-packages/mopidy/config/validators.py | 39 + .../site-packages/mopidy/core/__init__.py | 9 + .../site-packages/mopidy/core/actor.py | 276 + .../site-packages/mopidy/core/history.py | 73 + .../site-packages/mopidy/core/library.py | 351 + .../site-packages/mopidy/core/listener.py | 187 + .../site-packages/mopidy/core/mixer.py | 111 + .../site-packages/mopidy/core/playback.py | 558 ++ .../site-packages/mopidy/core/playlists.py | 280 + .../site-packages/mopidy/core/tracklist.py | 620 ++ .../site-packages/mopidy/exceptions.py | 53 + .../lib/python3.7/site-packages/mopidy/ext.py | 340 + .../site-packages/mopidy/file/__init__.py | 32 + .../site-packages/mopidy/file/backend.py | 18 + .../site-packages/mopidy/file/ext.conf | 19 + .../site-packages/mopidy/file/library.py | 148 + .../site-packages/mopidy/http/__init__.py | 53 + .../site-packages/mopidy/http/actor.py | 210 + .../mopidy/http/data/clients.html | 31 + .../mopidy/http/data/favicon.ico | Bin 0 -> 5997 bytes .../site-packages/mopidy/http/data/mopidy.css | 43 + .../site-packages/mopidy/http/ext.conf | 8 + .../site-packages/mopidy/http/handlers.py | 273 + .../site-packages/mopidy/httpclient.py | 50 + .../site-packages/mopidy/internal/__init__.py | 0 .../mopidy/internal/deprecation.py | 52 + .../site-packages/mopidy/internal/deps.py | 189 + .../mopidy/internal/formatting.py | 28 + .../site-packages/mopidy/internal/gi.py | 49 + .../site-packages/mopidy/internal/http.py | 57 + .../site-packages/mopidy/internal/jsonrpc.py | 387 + .../site-packages/mopidy/internal/log.py | 199 + .../site-packages/mopidy/internal/models.py | 142 + .../site-packages/mopidy/internal/network.py | 31 + .../site-packages/mopidy/internal/path.py | 104 + .../mopidy/internal/playlists.py | 137 + .../site-packages/mopidy/internal/process.py | 53 + .../site-packages/mopidy/internal/storage.py | 59 + .../site-packages/mopidy/internal/timer.py | 14 + .../mopidy/internal/validation.py | 133 + .../mopidy/internal/versioning.py | 29 + .../site-packages/mopidy/internal/xdg.py | 68 + .../site-packages/mopidy/listener.py | 45 + .../python3.7/site-packages/mopidy/local.py | 13 + .../site-packages/mopidy/m3u/__init__.py | 31 + .../site-packages/mopidy/m3u/backend.py | 13 + .../site-packages/mopidy/m3u/ext.conf | 6 + .../site-packages/mopidy/m3u/playlists.py | 174 + .../site-packages/mopidy/m3u/translator.py | 87 + .../python3.7/site-packages/mopidy/mixer.py | 152 + .../site-packages/mopidy/models/__init__.py | 361 + .../site-packages/mopidy/models/fields.py | 179 + .../site-packages/mopidy/models/immutable.py | 219 + .../site-packages/mopidy/models/serialize.py | 43 + .../mopidy/softwaremixer/__init__.py | 24 + .../mopidy/softwaremixer/ext.conf | 2 + .../mopidy/softwaremixer/mixer.py | 58 + .../site-packages/mopidy/stream/__init__.py | 30 + .../site-packages/mopidy/stream/actor.py | 187 + .../site-packages/mopidy/stream/ext.conf | 11 + .../site-packages/mopidy/zeroconf.py | 150 + .../pip-19.0.3-py3.7.egg/EGG-INFO/PKG-INFO | 73 + .../pip-19.0.3-py3.7.egg/EGG-INFO/SOURCES.txt | 391 + .../EGG-INFO/dependency_links.txt | 1 + .../EGG-INFO/entry_points.txt | 5 + .../EGG-INFO/not-zip-safe | 1 + .../EGG-INFO/top_level.txt | 1 + .../pip-19.0.3-py3.7.egg/pip/__init__.py | 1 + .../pip-19.0.3-py3.7.egg/pip/__main__.py | 19 + .../pip/_internal/__init__.py | 78 + .../pip/_internal/build_env.py | 215 + .../pip/_internal/cache.py | 224 + .../pip/_internal/cli/__init__.py | 4 + .../pip/_internal/cli/autocompletion.py | 152 + .../pip/_internal/cli/base_command.py | 341 + .../pip/_internal/cli/cmdoptions.py | 809 ++ .../pip/_internal/cli/main_parser.py | 104 + .../pip/_internal/cli/parser.py | 261 + .../pip/_internal/cli/status_codes.py | 8 + .../pip/_internal/commands/__init__.py | 79 + .../pip/_internal/commands/check.py | 41 + .../pip/_internal/commands/completion.py | 94 + .../pip/_internal/commands/configuration.py | 227 + .../pip/_internal/commands/download.py | 176 + .../pip/_internal/commands/freeze.py | 96 + .../pip/_internal/commands/hash.py | 57 + .../pip/_internal/commands/help.py | 37 + .../pip/_internal/commands/install.py | 566 ++ .../pip/_internal/commands/list.py | 301 + .../pip/_internal/commands/search.py | 135 + .../pip/_internal/commands/show.py | 168 + .../pip/_internal/commands/uninstall.py | 78 + .../pip/_internal/commands/wheel.py | 186 + .../pip/_internal/configuration.py | 387 + .../pip/_internal/download.py | 971 ++ .../pip/_internal/exceptions.py | 274 + .../pip/_internal/index.py | 990 ++ .../pip/_internal/locations.py | 211 + .../pip/_internal/models/__init__.py | 2 + .../pip/_internal/models/candidate.py | 31 + .../pip/_internal/models/format_control.py | 73 + .../pip/_internal/models/index.py | 31 + .../pip/_internal/models/link.py | 163 + .../pip/_internal/operations/__init__.py | 0 .../pip/_internal/operations/check.py | 155 + .../pip/_internal/operations/freeze.py | 247 + .../pip/_internal/operations/prepare.py | 413 + .../pip/_internal/pep425tags.py | 381 + .../pip/_internal/pyproject.py | 171 + .../pip/_internal/req/__init__.py | 77 + .../pip/_internal/req/constructors.py | 339 + .../pip/_internal/req/req_file.py | 382 + .../pip/_internal/req/req_install.py | 1021 ++ .../pip/_internal/req/req_set.py | 197 + .../pip/_internal/req/req_tracker.py | 88 + .../pip/_internal/req/req_uninstall.py | 596 ++ .../pip/_internal/resolve.py | 393 + .../pip/_internal/utils/__init__.py | 0 .../pip/_internal/utils/appdirs.py | 270 + .../pip/_internal/utils/compat.py | 264 + .../pip/_internal/utils/deprecation.py | 90 + .../pip/_internal/utils/encoding.py | 39 + .../pip/_internal/utils/filesystem.py | 30 + .../pip/_internal/utils/glibc.py | 93 + .../pip/_internal/utils/hashes.py | 115 + .../pip/_internal/utils/logging.py | 318 + .../pip/_internal/utils/misc.py | 1040 +++ .../pip/_internal/utils/models.py | 40 + .../pip/_internal/utils/outdated.py | 164 + .../pip/_internal/utils/packaging.py | 85 + .../pip/_internal/utils/setuptools_build.py | 8 + .../pip/_internal/utils/temp_dir.py | 155 + .../pip/_internal/utils/typing.py | 29 + .../pip/_internal/utils/ui.py | 441 + .../pip/_internal/vcs/__init__.py | 534 ++ .../pip/_internal/vcs/bazaar.py | 114 + .../pip/_internal/vcs/git.py | 369 + .../pip/_internal/vcs/mercurial.py | 103 + .../pip/_internal/vcs/subversion.py | 200 + .../pip/_internal/wheel.py | 1095 +++ .../pip/_vendor/__init__.py | 111 + .../pip/_vendor/appdirs.py | 604 ++ .../pip/_vendor/cachecontrol/__init__.py | 11 + .../pip/_vendor/cachecontrol/_cmd.py | 57 + .../pip/_vendor/cachecontrol/adapter.py | 133 + .../pip/_vendor/cachecontrol/cache.py | 39 + .../_vendor/cachecontrol/caches/__init__.py | 2 + .../_vendor/cachecontrol/caches/file_cache.py | 146 + .../cachecontrol/caches/redis_cache.py | 33 + .../pip/_vendor/cachecontrol/compat.py | 29 + .../pip/_vendor/cachecontrol/controller.py | 367 + .../pip/_vendor/cachecontrol/filewrapper.py | 80 + .../pip/_vendor/cachecontrol/heuristics.py | 135 + .../pip/_vendor/cachecontrol/serialize.py | 186 + .../pip/_vendor/cachecontrol/wrapper.py | 29 + .../pip/_vendor/certifi/__init__.py | 3 + .../pip/_vendor/certifi/__main__.py | 2 + .../pip/_vendor/certifi/cacert.pem | 4512 +++++++++ .../pip/_vendor/certifi/core.py | 20 + .../pip/_vendor/chardet/__init__.py | 39 + .../pip/_vendor/chardet/big5freq.py | 386 + .../pip/_vendor/chardet/big5prober.py | 47 + .../pip/_vendor/chardet/chardistribution.py | 233 + .../pip/_vendor/chardet/charsetgroupprober.py | 106 + .../pip/_vendor/chardet/charsetprober.py | 145 + .../pip/_vendor/chardet/cli/__init__.py | 1 + .../pip/_vendor/chardet/cli/chardetect.py | 85 + .../pip/_vendor/chardet/codingstatemachine.py | 88 + .../pip/_vendor/chardet/compat.py | 34 + .../pip/_vendor/chardet/cp949prober.py | 49 + .../pip/_vendor/chardet/enums.py | 76 + .../pip/_vendor/chardet/escprober.py | 101 + .../pip/_vendor/chardet/escsm.py | 246 + .../pip/_vendor/chardet/eucjpprober.py | 92 + .../pip/_vendor/chardet/euckrfreq.py | 195 + .../pip/_vendor/chardet/euckrprober.py | 47 + .../pip/_vendor/chardet/euctwfreq.py | 387 + .../pip/_vendor/chardet/euctwprober.py | 46 + .../pip/_vendor/chardet/gb2312freq.py | 283 + .../pip/_vendor/chardet/gb2312prober.py | 46 + .../pip/_vendor/chardet/hebrewprober.py | 292 + .../pip/_vendor/chardet/jisfreq.py | 325 + .../pip/_vendor/chardet/jpcntx.py | 233 + .../pip/_vendor/chardet/langbulgarianmodel.py | 228 + .../pip/_vendor/chardet/langcyrillicmodel.py | 333 + .../pip/_vendor/chardet/langgreekmodel.py | 225 + .../pip/_vendor/chardet/langhebrewmodel.py | 200 + .../pip/_vendor/chardet/langhungarianmodel.py | 225 + .../pip/_vendor/chardet/langthaimodel.py | 199 + .../pip/_vendor/chardet/langturkishmodel.py | 193 + .../pip/_vendor/chardet/latin1prober.py | 145 + .../pip/_vendor/chardet/mbcharsetprober.py | 91 + .../pip/_vendor/chardet/mbcsgroupprober.py | 54 + .../pip/_vendor/chardet/mbcssm.py | 572 ++ .../pip/_vendor/chardet/sbcharsetprober.py | 132 + .../pip/_vendor/chardet/sbcsgroupprober.py | 73 + .../pip/_vendor/chardet/sjisprober.py | 92 + .../pip/_vendor/chardet/universaldetector.py | 286 + .../pip/_vendor/chardet/utf8prober.py | 82 + .../pip/_vendor/chardet/version.py | 9 + .../pip/_vendor/colorama/__init__.py | 6 + .../pip/_vendor/colorama/ansi.py | 102 + .../pip/_vendor/colorama/ansitowin32.py | 257 + .../pip/_vendor/colorama/initialise.py | 80 + .../pip/_vendor/colorama/win32.py | 152 + .../pip/_vendor/colorama/winterm.py | 169 + .../pip/_vendor/distlib/__init__.py | 23 + .../pip/_vendor/distlib/_backport/__init__.py | 6 + .../pip/_vendor/distlib/_backport/misc.py | 41 + .../pip/_vendor/distlib/_backport/shutil.py | 761 ++ .../_vendor/distlib/_backport/sysconfig.cfg | 84 + .../_vendor/distlib/_backport/sysconfig.py | 788 ++ .../pip/_vendor/distlib/_backport/tarfile.py | 2607 ++++++ .../pip/_vendor/distlib/compat.py | 1120 +++ .../pip/_vendor/distlib/database.py | 1339 +++ .../pip/_vendor/distlib/index.py | 516 ++ .../pip/_vendor/distlib/locators.py | 1295 +++ .../pip/_vendor/distlib/manifest.py | 393 + .../pip/_vendor/distlib/markers.py | 131 + .../pip/_vendor/distlib/metadata.py | 1094 +++ .../pip/_vendor/distlib/resources.py | 355 + .../pip/_vendor/distlib/scripts.py | 417 + .../pip/_vendor/distlib/t32.exe | Bin 0 -> 92672 bytes .../pip/_vendor/distlib/t64.exe | Bin 0 -> 102400 bytes .../pip/_vendor/distlib/util.py | 1756 ++++ .../pip/_vendor/distlib/version.py | 736 ++ .../pip/_vendor/distlib/w32.exe | Bin 0 -> 89088 bytes .../pip/_vendor/distlib/w64.exe | Bin 0 -> 99328 bytes .../pip/_vendor/distlib/wheel.py | 988 ++ .../pip/_vendor/distro.py | 1197 +++ .../pip/_vendor/html5lib/__init__.py | 35 + .../pip/_vendor/html5lib/_ihatexml.py | 288 + .../pip/_vendor/html5lib/_inputstream.py | 923 ++ .../pip/_vendor/html5lib/_tokenizer.py | 1721 ++++ .../pip/_vendor/html5lib/_trie/__init__.py | 14 + .../pip/_vendor/html5lib/_trie/_base.py | 37 + .../pip/_vendor/html5lib/_trie/datrie.py | 44 + .../pip/_vendor/html5lib/_trie/py.py | 67 + .../pip/_vendor/html5lib/_utils.py | 124 + .../pip/_vendor/html5lib/constants.py | 2947 ++++++ .../pip/_vendor/html5lib/filters/__init__.py | 0 .../filters/alphabeticalattributes.py | 29 + .../pip/_vendor/html5lib/filters/base.py | 12 + .../html5lib/filters/inject_meta_charset.py | 73 + .../pip/_vendor/html5lib/filters/lint.py | 93 + .../_vendor/html5lib/filters/optionaltags.py | 207 + .../pip/_vendor/html5lib/filters/sanitizer.py | 896 ++ .../_vendor/html5lib/filters/whitespace.py | 38 + .../pip/_vendor/html5lib/html5parser.py | 2791 ++++++ .../pip/_vendor/html5lib/serializer.py | 409 + .../_vendor/html5lib/treeadapters/__init__.py | 30 + .../_vendor/html5lib/treeadapters/genshi.py | 54 + .../pip/_vendor/html5lib/treeadapters/sax.py | 50 + .../_vendor/html5lib/treebuilders/__init__.py | 88 + .../pip/_vendor/html5lib/treebuilders/base.py | 417 + .../pip/_vendor/html5lib/treebuilders/dom.py | 236 + .../_vendor/html5lib/treebuilders/etree.py | 340 + .../html5lib/treebuilders/etree_lxml.py | 366 + .../_vendor/html5lib/treewalkers/__init__.py | 154 + .../pip/_vendor/html5lib/treewalkers/base.py | 252 + .../pip/_vendor/html5lib/treewalkers/dom.py | 43 + .../pip/_vendor/html5lib/treewalkers/etree.py | 130 + .../html5lib/treewalkers/etree_lxml.py | 213 + .../_vendor/html5lib/treewalkers/genshi.py | 69 + .../pip/_vendor/idna/__init__.py | 2 + .../pip/_vendor/idna/codec.py | 118 + .../pip/_vendor/idna/compat.py | 12 + .../pip/_vendor/idna/core.py | 396 + .../pip/_vendor/idna/idnadata.py | 1979 ++++ .../pip/_vendor/idna/intranges.py | 53 + .../pip/_vendor/idna/package_data.py | 2 + .../pip/_vendor/idna/uts46data.py | 8205 +++++++++++++++++ .../pip/_vendor/ipaddress.py | 2419 +++++ .../pip/_vendor/lockfile/__init__.py | 347 + .../pip/_vendor/lockfile/linklockfile.py | 73 + .../pip/_vendor/lockfile/mkdirlockfile.py | 84 + .../pip/_vendor/lockfile/pidlockfile.py | 190 + .../pip/_vendor/lockfile/sqlitelockfile.py | 156 + .../pip/_vendor/lockfile/symlinklockfile.py | 70 + .../pip/_vendor/msgpack/__init__.py | 66 + .../pip/_vendor/msgpack/_version.py | 1 + .../pip/_vendor/msgpack/exceptions.py | 41 + .../pip/_vendor/msgpack/fallback.py | 977 ++ .../pip/_vendor/packaging/__about__.py | 27 + .../pip/_vendor/packaging/__init__.py | 26 + .../pip/_vendor/packaging/_compat.py | 31 + .../pip/_vendor/packaging/_structures.py | 68 + .../pip/_vendor/packaging/markers.py | 296 + .../pip/_vendor/packaging/requirements.py | 138 + .../pip/_vendor/packaging/specifiers.py | 749 ++ .../pip/_vendor/packaging/utils.py | 57 + .../pip/_vendor/packaging/version.py | 420 + .../pip/_vendor/pep517/__init__.py | 4 + .../pip/_vendor/pep517/_in_process.py | 207 + .../pip/_vendor/pep517/build.py | 108 + .../pip/_vendor/pep517/check.py | 202 + .../pip/_vendor/pep517/colorlog.py | 115 + .../pip/_vendor/pep517/compat.py | 23 + .../pip/_vendor/pep517/envbuild.py | 158 + .../pip/_vendor/pep517/wrappers.py | 163 + .../pip/_vendor/pkg_resources/__init__.py | 3171 +++++++ .../pip/_vendor/pkg_resources/py31compat.py | 23 + .../pip/_vendor/progress/__init__.py | 127 + .../pip/_vendor/progress/bar.py | 94 + .../pip/_vendor/progress/counter.py | 48 + .../pip/_vendor/progress/helpers.py | 91 + .../pip/_vendor/progress/spinner.py | 44 + .../pip/_vendor/pyparsing.py | 6452 +++++++++++++ .../pip/_vendor/pytoml/__init__.py | 4 + .../pip/_vendor/pytoml/core.py | 13 + .../pip/_vendor/pytoml/parser.py | 341 + .../pip/_vendor/pytoml/test.py | 30 + .../pip/_vendor/pytoml/utils.py | 67 + .../pip/_vendor/pytoml/writer.py | 106 + .../pip/_vendor/requests/__init__.py | 133 + .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 42 + .../pip/_vendor/requests/adapters.py | 533 ++ .../pip/_vendor/requests/api.py | 158 + .../pip/_vendor/requests/auth.py | 305 + .../pip/_vendor/requests/certs.py | 18 + .../pip/_vendor/requests/compat.py | 74 + .../pip/_vendor/requests/cookies.py | 549 ++ .../pip/_vendor/requests/exceptions.py | 126 + .../pip/_vendor/requests/help.py | 119 + .../pip/_vendor/requests/hooks.py | 34 + .../pip/_vendor/requests/models.py | 953 ++ .../pip/_vendor/requests/packages.py | 16 + .../pip/_vendor/requests/sessions.py | 770 ++ .../pip/_vendor/requests/status_codes.py | 120 + .../pip/_vendor/requests/structures.py | 103 + .../pip/_vendor/requests/utils.py | 977 ++ .../pip/_vendor/retrying.py | 267 + .../pip-19.0.3-py3.7.egg/pip/_vendor/six.py | 952 ++ .../pip/_vendor/urllib3/__init__.py | 92 + .../pip/_vendor/urllib3/_collections.py | 329 + .../pip/_vendor/urllib3/connection.py | 391 + .../pip/_vendor/urllib3/connectionpool.py | 896 ++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/_appengine_environ.py | 30 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 593 ++ .../contrib/_securetransport/low_level.py | 346 + .../pip/_vendor/urllib3/contrib/appengine.py | 289 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 111 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 466 + .../urllib3/contrib/securetransport.py | 804 ++ .../pip/_vendor/urllib3/contrib/socks.py | 192 + .../pip/_vendor/urllib3/exceptions.py | 246 + .../pip/_vendor/urllib3/fields.py | 178 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 5 + .../urllib3/packages/backports/__init__.py | 0 .../urllib3/packages/backports/makefile.py | 53 + .../pip/_vendor/urllib3/packages/six.py | 868 ++ .../packages/ssl_match_hostname/__init__.py | 19 + .../ssl_match_hostname/_implementation.py | 156 + .../pip/_vendor/urllib3/poolmanager.py | 450 + .../pip/_vendor/urllib3/request.py | 150 + .../pip/_vendor/urllib3/response.py | 705 ++ .../pip/_vendor/urllib3/util/__init__.py | 54 + .../pip/_vendor/urllib3/util/connection.py | 134 + .../pip/_vendor/urllib3/util/queue.py | 21 + .../pip/_vendor/urllib3/util/request.py | 118 + .../pip/_vendor/urllib3/util/response.py | 87 + .../pip/_vendor/urllib3/util/retry.py | 411 + .../pip/_vendor/urllib3/util/ssl_.py | 381 + .../pip/_vendor/urllib3/util/timeout.py | 242 + .../pip/_vendor/urllib3/util/url.py | 230 + .../pip/_vendor/urllib3/util/wait.py | 150 + .../pip/_vendor/webencodings/__init__.py | 342 + .../pip/_vendor/webencodings/labels.py | 231 + .../pip/_vendor/webencodings/mklabels.py | 59 + .../pip/_vendor/webencodings/tests.py | 153 + .../_vendor/webencodings/x_user_defined.py | 325 + .../python3.7/site-packages/pykka/__init__.py | 32 + .../python3.7/site-packages/pykka/_actor.py | 356 + .../site-packages/pykka/_compat/__init__.py | 36 + .../site-packages/pykka/_compat/await_py3.py | 8 + .../site-packages/pykka/_envelope.py | 23 + .../site-packages/pykka/_exceptions.py | 13 + .../python3.7/site-packages/pykka/_future.py | 264 + .../python3.7/site-packages/pykka/_proxy.py | 357 + .../lib/python3.7/site-packages/pykka/_ref.py | 171 + .../site-packages/pykka/_registry.py | 171 + .../site-packages/pykka/_threading.py | 97 + .../python3.7/site-packages/pykka/debug.py | 67 + .../python3.7/site-packages/pykka/eventlet.py | 107 + .../python3.7/site-packages/pykka/gevent.py | 73 + .../python3.7/site-packages/pykka/messages.py | 77 + .../requests-2.22.0.dist-info/INSTALLER | 1 + .../requests-2.22.0.dist-info/LICENSE | 13 + .../requests-2.22.0.dist-info/METADATA | 145 + .../requests-2.22.0.dist-info/RECORD | 42 + .../requests-2.22.0.dist-info/WHEEL | 6 + .../requests-2.22.0.dist-info/top_level.txt | 1 + .../site-packages/requests/__init__.py | 131 + .../site-packages/requests/__version__.py | 14 + .../site-packages/requests/_internal_utils.py | 42 + .../site-packages/requests/adapters.py | 533 ++ .../python3.7/site-packages/requests/api.py | 158 + .../python3.7/site-packages/requests/auth.py | 305 + .../python3.7/site-packages/requests/certs.py | 18 + .../site-packages/requests/compat.py | 70 + .../site-packages/requests/cookies.py | 549 ++ .../site-packages/requests/exceptions.py | 126 + .../python3.7/site-packages/requests/help.py | 119 + .../python3.7/site-packages/requests/hooks.py | 34 + .../site-packages/requests/models.py | 953 ++ .../site-packages/requests/packages.py | 14 + .../site-packages/requests/sessions.py | 770 ++ .../site-packages/requests/status_codes.py | 120 + .../site-packages/requests/structures.py | 103 + .../python3.7/site-packages/requests/utils.py | 977 ++ .../site-packages/setuptools-40.8.0-py3.7.egg | Bin 0 -> 571891 bytes .../python3.7/site-packages/setuptools.pth | 1 + .../site-packages/tornado/__init__.py | 26 + .../site-packages/tornado/_locale_data.py | 82 + .../python3.7/site-packages/tornado/auth.py | 1182 +++ .../site-packages/tornado/autoreload.py | 364 + .../site-packages/tornado/concurrent.py | 264 + .../site-packages/tornado/curl_httpclient.py | 572 ++ .../python3.7/site-packages/tornado/escape.py | 400 + .../python3.7/site-packages/tornado/gen.py | 845 ++ .../site-packages/tornado/http1connection.py | 836 ++ .../site-packages/tornado/httpclient.py | 781 ++ .../site-packages/tornado/httpserver.py | 398 + .../site-packages/tornado/httputil.py | 1144 +++ .../python3.7/site-packages/tornado/ioloop.py | 946 ++ .../site-packages/tornado/iostream.py | 1655 ++++ .../python3.7/site-packages/tornado/locale.py | 564 ++ .../python3.7/site-packages/tornado/locks.py | 570 ++ .../python3.7/site-packages/tornado/log.py | 336 + .../site-packages/tornado/netutil.py | 614 ++ .../site-packages/tornado/options.py | 726 ++ .../tornado/platform/__init__.py | 0 .../site-packages/tornado/platform/asyncio.py | 337 + .../site-packages/tornado/platform/auto.py | 32 + .../tornado/platform/caresresolver.py | 89 + .../tornado/platform/interface.py | 26 + .../site-packages/tornado/platform/posix.py | 29 + .../site-packages/tornado/platform/twisted.py | 131 + .../site-packages/tornado/platform/windows.py | 22 + .../site-packages/tornado/process.py | 373 + .../python3.7/site-packages/tornado/py.typed | 0 .../python3.7/site-packages/tornado/queues.py | 410 + .../site-packages/tornado/routing.py | 711 ++ .../tornado/simple_httpclient.py | 692 ++ .../site-packages/tornado/tcpclient.py | 334 + .../site-packages/tornado/tcpserver.py | 330 + .../site-packages/tornado/template.py | 1043 +++ .../site-packages/tornado/test/__init__.py | 0 .../site-packages/tornado/test/__main__.py | 12 + .../tornado/test/asyncio_test.py | 190 + .../site-packages/tornado/test/auth_test.py | 609 ++ .../tornado/test/autoreload_test.py | 127 + .../tornado/test/concurrent_test.py | 209 + .../tornado/test/csv_translations/fr_FR.csv | 1 + .../tornado/test/curl_httpclient_test.py | 130 + .../site-packages/tornado/test/escape_test.py | 322 + .../site-packages/tornado/test/gen_test.py | 1111 +++ .../fr_FR/LC_MESSAGES/tornado_test.mo | Bin 0 -> 665 bytes .../fr_FR/LC_MESSAGES/tornado_test.po | 47 + .../tornado/test/http1connection_test.py | 58 + .../tornado/test/httpclient_test.py | 832 ++ .../tornado/test/httpserver_test.py | 1296 +++ .../tornado/test/httputil_test.py | 522 ++ .../site-packages/tornado/test/import_test.py | 66 + .../site-packages/tornado/test/ioloop_test.py | 719 ++ .../tornado/test/iostream_test.py | 1198 +++ .../site-packages/tornado/test/locale_test.py | 151 + .../site-packages/tornado/test/locks_test.py | 535 ++ .../site-packages/tornado/test/log_test.py | 245 + .../tornado/test/netutil_test.py | 226 + .../tornado/test/options_test.cfg | 7 + .../tornado/test/options_test.py | 329 + .../tornado/test/options_test_types.cfg | 11 + .../tornado/test/options_test_types_str.cfg | 8 + .../tornado/test/process_test.py | 265 + .../site-packages/tornado/test/queues_test.py | 427 + .../tornado/test/resolve_test_helper.py | 10 + .../tornado/test/routing_test.py | 276 + .../site-packages/tornado/test/runtests.py | 236 + .../tornado/test/simple_httpclient_test.py | 832 ++ .../tornado/test/static/dir/index.html | 1 + .../tornado/test/static/robots.txt | 2 + .../tornado/test/static/sample.xml | 23 + .../tornado/test/static/sample.xml.bz2 | Bin 0 -> 285 bytes .../tornado/test/static/sample.xml.gz | Bin 0 -> 264 bytes .../site-packages/tornado/test/static_foo.txt | 2 + .../tornado/test/tcpclient_test.py | 435 + .../tornado/test/tcpserver_test.py | 192 + .../tornado/test/template_test.py | 536 ++ .../tornado/test/templates/utf8.html | 1 + .../site-packages/tornado/test/test.crt | 20 + .../site-packages/tornado/test/test.key | 28 + .../tornado/test/testing_test.py | 350 + .../tornado/test/twisted_test.py | 236 + .../site-packages/tornado/test/util.py | 114 + .../site-packages/tornado/test/util_test.py | 309 + .../site-packages/tornado/test/web_test.py | 3138 +++++++ .../tornado/test/websocket_test.py | 822 ++ .../tornado/test/windows_test.py | 24 + .../site-packages/tornado/test/wsgi_test.py | 20 + .../site-packages/tornado/testing.py | 790 ++ .../python3.7/site-packages/tornado/util.py | 472 + .../python3.7/site-packages/tornado/web.py | 3585 +++++++ .../site-packages/tornado/websocket.py | 1663 ++++ .../python3.7/site-packages/tornado/wsgi.py | 199 + .../uritools-3.0.0.dist-info/INSTALLER | 1 + .../uritools-3.0.0.dist-info/LICENSE | 20 + .../uritools-3.0.0.dist-info/METADATA | 124 + .../uritools-3.0.0.dist-info/RECORD | 22 + .../uritools-3.0.0.dist-info/WHEEL | 5 + .../uritools-3.0.0.dist-info/top_level.txt | 1 + .../site-packages/uritools/__init__.py | 40 + .../python3.7/site-packages/uritools/chars.py | 23 + .../site-packages/uritools/classify.py | 33 + .../site-packages/uritools/compose.py | 204 + .../site-packages/uritools/defrag.py | 41 + .../site-packages/uritools/encoding.py | 53 + .../python3.7/site-packages/uritools/join.py | 14 + .../python3.7/site-packages/uritools/split.py | 399 + .../urllib3-1.25.7.dist-info/INSTALLER | 1 + .../urllib3-1.25.7.dist-info/LICENSE.txt | 21 + .../urllib3-1.25.7.dist-info/METADATA | 1229 +++ .../urllib3-1.25.7.dist-info/RECORD | 78 + .../urllib3-1.25.7.dist-info/WHEEL | 6 + .../urllib3-1.25.7.dist-info/top_level.txt | 1 + .../site-packages/urllib3/__init__.py | 86 + .../site-packages/urllib3/_collections.py | 336 + .../site-packages/urllib3/connection.py | 448 + .../site-packages/urllib3/connectionpool.py | 1051 +++ .../site-packages/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 493 + .../contrib/_securetransport/low_level.py | 328 + .../urllib3/contrib/appengine.py | 314 + .../site-packages/urllib3/contrib/ntlmpool.py | 121 + .../urllib3/contrib/pyopenssl.py | 498 + .../urllib3/contrib/securetransport.py | 859 ++ .../site-packages/urllib3/contrib/socks.py | 210 + .../site-packages/urllib3/exceptions.py | 255 + .../python3.7/site-packages/urllib3/fields.py | 273 + .../site-packages/urllib3/filepost.py | 98 + .../urllib3/packages/__init__.py | 5 + .../urllib3/packages/backports/__init__.py | 0 .../urllib3/packages/backports/makefile.py | 52 + .../site-packages/urllib3/packages/six.py | 1021 ++ .../packages/ssl_match_hostname/__init__.py | 19 + .../ssl_match_hostname/_implementation.py | 160 + .../site-packages/urllib3/poolmanager.py | 470 + .../site-packages/urllib3/request.py | 171 + .../site-packages/urllib3/response.py | 809 ++ .../site-packages/urllib3/util/__init__.py | 46 + .../site-packages/urllib3/util/connection.py | 138 + .../site-packages/urllib3/util/queue.py | 21 + .../site-packages/urllib3/util/request.py | 135 + .../site-packages/urllib3/util/response.py | 86 + .../site-packages/urllib3/util/retry.py | 450 + .../site-packages/urllib3/util/ssl_.py | 407 + .../site-packages/urllib3/util/timeout.py | 258 + .../site-packages/urllib3/util/url.py | 436 + .../site-packages/urllib3/util/wait.py | 153 + venv/lib64 | 1 + venv/pyvenv.cfg | 3 + 691 files changed, 201846 insertions(+), 598 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 100755 venv/bin/chardetect create mode 100755 venv/bin/easy_install create mode 100755 venv/bin/easy_install-3.7 create mode 100755 venv/bin/mopidy create mode 100755 venv/bin/pip create mode 100755 venv/bin/pip3 create mode 100755 venv/bin/pip3.7 create mode 120000 venv/bin/python create mode 120000 venv/bin/python3 create mode 120000 venv/bin/python3.7 create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/LICENSE create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/LICENSE create mode 100644 venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/DESCRIPTION.rst create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/metadata.json create mode 100644 venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/certifi/__init__.py create mode 100644 venv/lib/python3.7/site-packages/certifi/__main__.py create mode 100644 venv/lib/python3.7/site-packages/certifi/cacert.pem create mode 100644 venv/lib/python3.7/site-packages/certifi/core.py create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/metadata.json create mode 100644 venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/chardet/__init__.py create mode 100644 venv/lib/python3.7/site-packages/chardet/big5freq.py create mode 100644 venv/lib/python3.7/site-packages/chardet/big5prober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/chardistribution.py create mode 100644 venv/lib/python3.7/site-packages/chardet/charsetgroupprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/charsetprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/cli/__init__.py create mode 100644 venv/lib/python3.7/site-packages/chardet/cli/chardetect.py create mode 100644 venv/lib/python3.7/site-packages/chardet/codingstatemachine.py create mode 100644 venv/lib/python3.7/site-packages/chardet/compat.py create mode 100644 venv/lib/python3.7/site-packages/chardet/cp949prober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/enums.py create mode 100644 venv/lib/python3.7/site-packages/chardet/escprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/escsm.py create mode 100644 venv/lib/python3.7/site-packages/chardet/eucjpprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/euckrfreq.py create mode 100644 venv/lib/python3.7/site-packages/chardet/euckrprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/euctwfreq.py create mode 100644 venv/lib/python3.7/site-packages/chardet/euctwprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/gb2312freq.py create mode 100644 venv/lib/python3.7/site-packages/chardet/gb2312prober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/hebrewprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/jisfreq.py create mode 100644 venv/lib/python3.7/site-packages/chardet/jpcntx.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langbulgarianmodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langcyrillicmodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langgreekmodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langhebrewmodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langhungarianmodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langthaimodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/langturkishmodel.py create mode 100644 venv/lib/python3.7/site-packages/chardet/latin1prober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/mbcharsetprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/mbcsgroupprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/mbcssm.py create mode 100644 venv/lib/python3.7/site-packages/chardet/sbcharsetprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/sbcsgroupprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/sjisprober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/universaldetector.py create mode 100644 venv/lib/python3.7/site-packages/chardet/utf8prober.py create mode 100644 venv/lib/python3.7/site-packages/chardet/version.py create mode 100644 venv/lib/python3.7/site-packages/easy-install.pth create mode 100644 venv/lib/python3.7/site-packages/idna-2.8.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/idna-2.8.dist-info/LICENSE.rst create mode 100644 venv/lib/python3.7/site-packages/idna-2.8.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/idna-2.8.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/idna-2.8.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/idna-2.8.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/idna/__init__.py create mode 100644 venv/lib/python3.7/site-packages/idna/codec.py create mode 100644 venv/lib/python3.7/site-packages/idna/compat.py create mode 100644 venv/lib/python3.7/site-packages/idna/core.py create mode 100644 venv/lib/python3.7/site-packages/idna/idnadata.py create mode 100644 venv/lib/python3.7/site-packages/idna/intranges.py create mode 100644 venv/lib/python3.7/site-packages/idna/package_data.py create mode 100644 venv/lib/python3.7/site-packages/idna/uts46data.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/__main__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/actor.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/constants.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/listener.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/scan.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/tags.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/audio/utils.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/backend.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/commands.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/config/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/config/default.conf create mode 100644 venv/lib/python3.7/site-packages/mopidy/config/keyring.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/config/schemas.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/config/types.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/config/validators.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/actor.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/history.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/library.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/listener.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/mixer.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/playback.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/playlists.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/core/tracklist.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/ext.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/file/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/file/backend.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/file/ext.conf create mode 100644 venv/lib/python3.7/site-packages/mopidy/file/library.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/actor.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/data/clients.html create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/data/favicon.ico create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/data/mopidy.css create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/ext.conf create mode 100644 venv/lib/python3.7/site-packages/mopidy/http/handlers.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/httpclient.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/deprecation.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/deps.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/formatting.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/gi.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/http.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/jsonrpc.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/log.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/models.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/network.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/path.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/playlists.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/process.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/storage.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/timer.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/validation.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/versioning.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/internal/xdg.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/listener.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/local.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/m3u/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/m3u/backend.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/m3u/ext.conf create mode 100644 venv/lib/python3.7/site-packages/mopidy/m3u/playlists.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/m3u/translator.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/mixer.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/models/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/models/fields.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/models/immutable.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/models/serialize.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/softwaremixer/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/softwaremixer/ext.conf create mode 100644 venv/lib/python3.7/site-packages/mopidy/softwaremixer/mixer.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/stream/__init__.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/stream/actor.py create mode 100644 venv/lib/python3.7/site-packages/mopidy/stream/ext.conf create mode 100644 venv/lib/python3.7/site-packages/mopidy/zeroconf.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/PKG-INFO create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/SOURCES.txt create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/dependency_links.txt create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/entry_points.txt create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/not-zip-safe create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__main__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/build_env.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cache.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/parser.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/check.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/completion.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/download.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/hash.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/help.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/install.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/list.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/search.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/show.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/uninstall.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/wheel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/configuration.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/download.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/index.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/locations.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/candidate.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/format_control.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/index.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/link.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/check.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/freeze.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/prepare.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pep425tags.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pyproject.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/constructors.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_file.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_install.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_set.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_tracker.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_uninstall.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/resolve.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/appdirs.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/deprecation.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/encoding.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/filesystem.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/glibc.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/hashes.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/logging.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/misc.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/models.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/outdated.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/packaging.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/setuptools_build.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/temp_dir.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/typing.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/ui.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/bazaar.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/git.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/mercurial.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/subversion.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/wheel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/appdirs.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__main__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/cacert.pem create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/core.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5freq.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5prober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/chardistribution.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetgroupprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/chardetect.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/codingstatemachine.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cp949prober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/enums.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escsm.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/eucjpprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrfreq.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwfreq.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312freq.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312prober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/hebrewprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jisfreq.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jpcntx.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langbulgarianmodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langcyrillicmodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langgreekmodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhebrewmodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhungarianmodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langthaimodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langturkishmodel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/latin1prober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcharsetprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcsgroupprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcssm.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcharsetprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcsgroupprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sjisprober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/universaldetector.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/utf8prober.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/version.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansi.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansitowin32.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/initialise.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/win32.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/winterm.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/misc.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/shutil.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.cfg create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/tarfile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/database.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/index.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/locators.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/manifest.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/markers.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/metadata.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/resources.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/scripts.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/t32.exe create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/t64.exe create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/util.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/version.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/w32.exe create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/w64.exe create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/wheel.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distro.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_ihatexml.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_inputstream.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_tokenizer.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/_base.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/datrie.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/py.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_utils.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/constants.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/alphabeticalattributes.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/base.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/inject_meta_charset.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/lint.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/optionaltags.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/sanitizer.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/whitespace.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/html5parser.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/serializer.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/genshi.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/sax.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/base.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/dom.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree_lxml.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/base.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/dom.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree_lxml.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/genshi.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/codec.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/core.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/idnadata.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/intranges.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/package_data.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/uts46data.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/ipaddress.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/linklockfile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/mkdirlockfile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/pidlockfile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/sqlitelockfile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/symlinklockfile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/_version.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/fallback.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/version.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/_in_process.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/build.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/check.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/colorlog.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/envbuild.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/wrappers.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/py31compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/bar.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/counter.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/helpers.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/spinner.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pyparsing.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/core.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/parser.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/test.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/utils.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/writer.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__version__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/adapters.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/api.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/auth.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/certs.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/compat.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/cookies.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/help.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/hooks.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/models.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/packages.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/sessions.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/status_codes.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/structures.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/utils.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/retrying.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/six.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/_collections.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connection.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/fields.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/filepost.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/request.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/response.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/request.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/response.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/url.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/labels.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/mklabels.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/tests.py create mode 100644 venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/x_user_defined.py create mode 100644 venv/lib/python3.7/site-packages/pykka/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_actor.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_compat/__init__.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_compat/await_py3.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_envelope.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_exceptions.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_future.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_proxy.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_ref.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_registry.py create mode 100644 venv/lib/python3.7/site-packages/pykka/_threading.py create mode 100644 venv/lib/python3.7/site-packages/pykka/debug.py create mode 100644 venv/lib/python3.7/site-packages/pykka/eventlet.py create mode 100644 venv/lib/python3.7/site-packages/pykka/gevent.py create mode 100644 venv/lib/python3.7/site-packages/pykka/messages.py create mode 100644 venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE create mode 100644 venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/requests/__init__.py create mode 100644 venv/lib/python3.7/site-packages/requests/__version__.py create mode 100644 venv/lib/python3.7/site-packages/requests/_internal_utils.py create mode 100644 venv/lib/python3.7/site-packages/requests/adapters.py create mode 100644 venv/lib/python3.7/site-packages/requests/api.py create mode 100644 venv/lib/python3.7/site-packages/requests/auth.py create mode 100644 venv/lib/python3.7/site-packages/requests/certs.py create mode 100644 venv/lib/python3.7/site-packages/requests/compat.py create mode 100644 venv/lib/python3.7/site-packages/requests/cookies.py create mode 100644 venv/lib/python3.7/site-packages/requests/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/requests/help.py create mode 100644 venv/lib/python3.7/site-packages/requests/hooks.py create mode 100644 venv/lib/python3.7/site-packages/requests/models.py create mode 100644 venv/lib/python3.7/site-packages/requests/packages.py create mode 100644 venv/lib/python3.7/site-packages/requests/sessions.py create mode 100644 venv/lib/python3.7/site-packages/requests/status_codes.py create mode 100644 venv/lib/python3.7/site-packages/requests/structures.py create mode 100644 venv/lib/python3.7/site-packages/requests/utils.py create mode 100644 venv/lib/python3.7/site-packages/setuptools-40.8.0-py3.7.egg create mode 100644 venv/lib/python3.7/site-packages/setuptools.pth create mode 100644 venv/lib/python3.7/site-packages/tornado/__init__.py create mode 100644 venv/lib/python3.7/site-packages/tornado/_locale_data.py create mode 100644 venv/lib/python3.7/site-packages/tornado/auth.py create mode 100644 venv/lib/python3.7/site-packages/tornado/autoreload.py create mode 100644 venv/lib/python3.7/site-packages/tornado/concurrent.py create mode 100644 venv/lib/python3.7/site-packages/tornado/curl_httpclient.py create mode 100644 venv/lib/python3.7/site-packages/tornado/escape.py create mode 100644 venv/lib/python3.7/site-packages/tornado/gen.py create mode 100644 venv/lib/python3.7/site-packages/tornado/http1connection.py create mode 100644 venv/lib/python3.7/site-packages/tornado/httpclient.py create mode 100644 venv/lib/python3.7/site-packages/tornado/httpserver.py create mode 100644 venv/lib/python3.7/site-packages/tornado/httputil.py create mode 100644 venv/lib/python3.7/site-packages/tornado/ioloop.py create mode 100644 venv/lib/python3.7/site-packages/tornado/iostream.py create mode 100644 venv/lib/python3.7/site-packages/tornado/locale.py create mode 100644 venv/lib/python3.7/site-packages/tornado/locks.py create mode 100644 venv/lib/python3.7/site-packages/tornado/log.py create mode 100644 venv/lib/python3.7/site-packages/tornado/netutil.py create mode 100644 venv/lib/python3.7/site-packages/tornado/options.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/__init__.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/asyncio.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/auto.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/caresresolver.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/interface.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/posix.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/twisted.py create mode 100644 venv/lib/python3.7/site-packages/tornado/platform/windows.py create mode 100644 venv/lib/python3.7/site-packages/tornado/process.py create mode 100644 venv/lib/python3.7/site-packages/tornado/py.typed create mode 100644 venv/lib/python3.7/site-packages/tornado/queues.py create mode 100644 venv/lib/python3.7/site-packages/tornado/routing.py create mode 100644 venv/lib/python3.7/site-packages/tornado/simple_httpclient.py create mode 100644 venv/lib/python3.7/site-packages/tornado/tcpclient.py create mode 100644 venv/lib/python3.7/site-packages/tornado/tcpserver.py create mode 100644 venv/lib/python3.7/site-packages/tornado/template.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/__init__.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/__main__.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/asyncio_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/auth_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/autoreload_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/concurrent_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/csv_translations/fr_FR.csv create mode 100644 venv/lib/python3.7/site-packages/tornado/test/curl_httpclient_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/escape_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/gen_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.mo create mode 100644 venv/lib/python3.7/site-packages/tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.po create mode 100644 venv/lib/python3.7/site-packages/tornado/test/http1connection_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/httpclient_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/httpserver_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/httputil_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/import_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/ioloop_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/iostream_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/locale_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/locks_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/log_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/netutil_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/options_test.cfg create mode 100644 venv/lib/python3.7/site-packages/tornado/test/options_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/options_test_types.cfg create mode 100644 venv/lib/python3.7/site-packages/tornado/test/options_test_types_str.cfg create mode 100644 venv/lib/python3.7/site-packages/tornado/test/process_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/queues_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/resolve_test_helper.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/routing_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/runtests.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/simple_httpclient_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/static/dir/index.html create mode 100644 venv/lib/python3.7/site-packages/tornado/test/static/robots.txt create mode 100644 venv/lib/python3.7/site-packages/tornado/test/static/sample.xml create mode 100644 venv/lib/python3.7/site-packages/tornado/test/static/sample.xml.bz2 create mode 100644 venv/lib/python3.7/site-packages/tornado/test/static/sample.xml.gz create mode 100644 venv/lib/python3.7/site-packages/tornado/test/static_foo.txt create mode 100644 venv/lib/python3.7/site-packages/tornado/test/tcpclient_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/tcpserver_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/template_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/templates/utf8.html create mode 100644 venv/lib/python3.7/site-packages/tornado/test/test.crt create mode 100644 venv/lib/python3.7/site-packages/tornado/test/test.key create mode 100644 venv/lib/python3.7/site-packages/tornado/test/testing_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/twisted_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/util.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/util_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/web_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/websocket_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/windows_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/test/wsgi_test.py create mode 100644 venv/lib/python3.7/site-packages/tornado/testing.py create mode 100644 venv/lib/python3.7/site-packages/tornado/util.py create mode 100644 venv/lib/python3.7/site-packages/tornado/web.py create mode 100644 venv/lib/python3.7/site-packages/tornado/websocket.py create mode 100644 venv/lib/python3.7/site-packages/tornado/wsgi.py create mode 100644 venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/LICENSE create mode 100644 venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/uritools/__init__.py create mode 100644 venv/lib/python3.7/site-packages/uritools/chars.py create mode 100644 venv/lib/python3.7/site-packages/uritools/classify.py create mode 100644 venv/lib/python3.7/site-packages/uritools/compose.py create mode 100644 venv/lib/python3.7/site-packages/uritools/defrag.py create mode 100644 venv/lib/python3.7/site-packages/uritools/encoding.py create mode 100644 venv/lib/python3.7/site-packages/uritools/join.py create mode 100644 venv/lib/python3.7/site-packages/uritools/split.py create mode 100644 venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/INSTALLER create mode 100644 venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/METADATA create mode 100644 venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/RECORD create mode 100644 venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/WHEEL create mode 100644 venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/top_level.txt create mode 100644 venv/lib/python3.7/site-packages/urllib3/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/_collections.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/connection.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/connectionpool.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/appengine.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/exceptions.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/fields.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/filepost.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/packages/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/packages/six.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/poolmanager.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/request.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/response.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/__init__.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/connection.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/queue.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/request.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/response.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/retry.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/timeout.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/url.py create mode 100644 venv/lib/python3.7/site-packages/urllib3/util/wait.py create mode 120000 venv/lib64 create mode 100644 venv/pyvenv.cfg diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..78c1bf0 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index eae431d..e9a01b1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,10 @@ - + + + - \ No newline at end of file diff --git a/.idea/mopidy-radionet.iml b/.idea/mopidy-radionet.iml index 03b0fee..21552fe 100644 --- a/.idea/mopidy-radionet.iml +++ b/.idea/mopidy-radionet.iml @@ -1,11 +1,10 @@ - - + + + + - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d9c7941..5ae612a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,12 +2,14 @@ - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/mopidy_radionet/__init__.py b/mopidy_radionet/__init__.py index 43db377..e242a32 100644 --- a/mopidy_radionet/__init__.py +++ b/mopidy_radionet/__init__.py @@ -6,7 +6,7 @@ import os from mopidy import config, ext -__version__ = '0.1.3' +__version__ = '0.1.4' logger = logging.getLogger(__name__) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index a6d3dfe..f4cdc84 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -81,8 +81,8 @@ class RadioNetClient(object): try: tmp_str = self.session.get(self.base_url) - m = re.search('apiKey ?= ?[\'|"](.*)[\'|"];', tmp_str.content) - self.api_key = m.group(1).encode() + m = re.search('apiKey ?= ?[\'|"](.*)[\'|"];', tmp_str.content.decode()) + self.api_key = m.group(1) logger.info('Radio.net: APIKEY %s' % self.api_key) except Exception: logger.error('Radio.net: Failed to connect %s retrying' diff --git a/setup.py b/setup.py index 911472e..bc2a294 100644 --- a/setup.py +++ b/setup.py @@ -23,12 +23,12 @@ setup( packages=find_packages(exclude=['tests', 'tests.*']), zip_safe=False, include_package_data=True, - python_requires='>= 2.7, < 3', + python_requires='>= 3.7', install_requires=[ + 'Mopidy >= 3.0.0', + 'Pykka >= 2.0.1', 'setuptools', - 'tornado >= 4.4, < 5', # Tornado 5 requires Python >= 2.7.9 - 'Mopidy >= 1.0', - 'Pykka >= 1.1', + 'uritools >= 1.0' ], entry_points={ 'mopidy.ext': [ @@ -40,7 +40,8 @@ setup( 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', 'Topic :: Multimedia :: Sound/Audio :: Players', ], ) diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 0000000..6e04759 --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,76 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/home/mariusz/IdeaProjects/mopidy-radionet/venv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + if [ "x(venv) " != x ] ; then + PS1="(venv) ${PS1:-}" + else + if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then + # special case for Aspen magic directories + # see http://www.zetadev.com/software/aspen/ + PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" + else + PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" + fi + fi + export PS1 +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r +fi diff --git a/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 0000000..2914e2d --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,37 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/home/mariusz/IdeaProjects/mopidy-radionet/venv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + if ("venv" != "") then + set env_name = "venv" + else + if (`basename "VIRTUAL_ENV"` == "__") then + # special case for Aspen magic directories + # see http://www.zetadev.com/software/aspen/ + set env_name = `basename \`dirname "$VIRTUAL_ENV"\`` + else + set env_name = `basename "$VIRTUAL_ENV"` + endif + endif + set prompt = "[$env_name] $prompt" + unset env_name +endif + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 0000000..61c656d --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,75 @@ +# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org) +# you cannot run it directly + +function deactivate -d "Exit virtualenv and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + functions -e fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + + set -e VIRTUAL_ENV + if test "$argv[1]" != "nondestructive" + # Self destruct! + functions -e deactivate + end +end + +# unset irrelevant variables +deactivate nondestructive + +set -gx VIRTUAL_ENV "/home/mariusz/IdeaProjects/mopidy-radionet/venv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# unset PYTHONHOME if set +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # save the current fish_prompt function as the function _old_fish_prompt + functions -c fish_prompt _old_fish_prompt + + # with the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command + set -l old_status $status + + # Prompt override? + if test -n "(venv) " + printf "%s%s" "(venv) " (set_color normal) + else + # ...Otherwise, prepend env + set -l _checkbase (basename "$VIRTUAL_ENV") + if test $_checkbase = "__" + # special case for Aspen magic directories + # see http://www.zetadev.com/software/aspen/ + printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal) + else + printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal) + end + end + + # Restore the return status of the previous command. + echo "exit $old_status" | . + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/venv/bin/chardetect b/venv/bin/chardetect new file mode 100755 index 0000000..3f12e57 --- /dev/null +++ b/venv/bin/chardetect @@ -0,0 +1,10 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from chardet.cli.chardetect import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/easy_install b/venv/bin/easy_install new file mode 100755 index 0000000..c8ef04c --- /dev/null +++ b/venv/bin/easy_install @@ -0,0 +1,12 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==40.8.0','console_scripts','easy_install' +__requires__ = 'setuptools==40.8.0' +import re +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point('setuptools==40.8.0', 'console_scripts', 'easy_install')() + ) diff --git a/venv/bin/easy_install-3.7 b/venv/bin/easy_install-3.7 new file mode 100755 index 0000000..2c5c8ed --- /dev/null +++ b/venv/bin/easy_install-3.7 @@ -0,0 +1,12 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==40.8.0','console_scripts','easy_install-3.7' +__requires__ = 'setuptools==40.8.0' +import re +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point('setuptools==40.8.0', 'console_scripts', 'easy_install-3.7')() + ) diff --git a/venv/bin/mopidy b/venv/bin/mopidy new file mode 100755 index 0000000..6c233e8 --- /dev/null +++ b/venv/bin/mopidy @@ -0,0 +1,10 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from mopidy.__main__ import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip b/venv/bin/pip new file mode 100755 index 0000000..9b6a41d --- /dev/null +++ b/venv/bin/pip @@ -0,0 +1,12 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip' +__requires__ = 'pip==19.0.3' +import re +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point('pip==19.0.3', 'console_scripts', 'pip')() + ) diff --git a/venv/bin/pip3 b/venv/bin/pip3 new file mode 100755 index 0000000..84fd679 --- /dev/null +++ b/venv/bin/pip3 @@ -0,0 +1,12 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip3' +__requires__ = 'pip==19.0.3' +import re +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point('pip==19.0.3', 'console_scripts', 'pip3')() + ) diff --git a/venv/bin/pip3.7 b/venv/bin/pip3.7 new file mode 100755 index 0000000..8d254a8 --- /dev/null +++ b/venv/bin/pip3.7 @@ -0,0 +1,12 @@ +#!/home/mariusz/IdeaProjects/mopidy-radionet/venv/bin/python +# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip3.7' +__requires__ = 'pip==19.0.3' +import re +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point('pip==19.0.3', 'console_scripts', 'pip3.7')() + ) diff --git a/venv/bin/python b/venv/bin/python new file mode 120000 index 0000000..940bee3 --- /dev/null +++ b/venv/bin/python @@ -0,0 +1 @@ +python3.7 \ No newline at end of file diff --git a/venv/bin/python3 b/venv/bin/python3 new file mode 120000 index 0000000..940bee3 --- /dev/null +++ b/venv/bin/python3 @@ -0,0 +1 @@ +python3.7 \ No newline at end of file diff --git a/venv/bin/python3.7 b/venv/bin/python3.7 new file mode 120000 index 0000000..f097b0e --- /dev/null +++ b/venv/bin/python3.7 @@ -0,0 +1 @@ +/usr/bin/python3.7 \ No newline at end of file diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/LICENSE b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/METADATA b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/METADATA new file mode 100644 index 0000000..5566713 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/METADATA @@ -0,0 +1,159 @@ +Metadata-Version: 2.1 +Name: Mopidy +Version: 3.0.1 +Summary: Mopidy is an extensible music server written in Python +Home-page: https://mopidy.com/ +Author: Stein Magnus Jodal +Author-email: stein.magnus@jodal.no +License: Apache License, Version 2.0 +Project-URL: Documentation, https://docs.mopidy.com/ +Project-URL: Discourse forum, https://discourse.mopidy.com/ +Project-URL: Zulip chat, https://mopidy.zulipchat.com/ +Project-URL: Source, https://github.com/mopidy/mopidy +Project-URL: Issues, https://github.com/mopidy/mopidy/issues +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: No Input/Output (Daemon) +Classifier: Intended Audience :: End Users/Desktop +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Multimedia :: Sound/Audio :: Players +Requires-Python: >=3.7 +Requires-Dist: Pykka (>=2.0.1) +Requires-Dist: requests (>=2.0) +Requires-Dist: setuptools +Requires-Dist: tornado (>=4.4) +Provides-Extra: dev +Requires-Dist: pygraphviz ; extra == 'dev' +Requires-Dist: sphinx ; extra == 'dev' +Requires-Dist: sphinx-rtd-theme ; extra == 'dev' +Requires-Dist: black ; extra == 'dev' +Requires-Dist: check-manifest ; extra == 'dev' +Requires-Dist: flake8 ; extra == 'dev' +Requires-Dist: flake8-bugbear ; extra == 'dev' +Requires-Dist: flake8-import-order ; extra == 'dev' +Requires-Dist: isort[pyproject] ; extra == 'dev' +Requires-Dist: pep8-naming ; extra == 'dev' +Requires-Dist: twine ; extra == 'dev' +Requires-Dist: wheel ; extra == 'dev' +Requires-Dist: pytest ; extra == 'dev' +Requires-Dist: pytest-cov ; extra == 'dev' +Requires-Dist: responses ; extra == 'dev' +Provides-Extra: docs +Requires-Dist: pygraphviz ; extra == 'docs' +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: sphinx-rtd-theme ; extra == 'docs' +Provides-Extra: lint +Requires-Dist: black ; extra == 'lint' +Requires-Dist: check-manifest ; extra == 'lint' +Requires-Dist: flake8 ; extra == 'lint' +Requires-Dist: flake8-bugbear ; extra == 'lint' +Requires-Dist: flake8-import-order ; extra == 'lint' +Requires-Dist: isort[pyproject] ; extra == 'lint' +Requires-Dist: pep8-naming ; extra == 'lint' +Provides-Extra: release +Requires-Dist: twine ; extra == 'release' +Requires-Dist: wheel ; extra == 'release' +Provides-Extra: test +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: pytest-cov ; extra == 'test' +Requires-Dist: responses ; extra == 'test' + +****** +Mopidy +****** + +`Mopidy`_ is an extensible music server written in Python. + +Mopidy plays music from local disk, Spotify, SoundCloud, Google Play Music, and +more. You edit the playlist from any phone, tablet, or computer using a variety +of MPD and web clients. + +**Stream music from the cloud** + +Vanilla Mopidy only plays music from files and radio streams. Through +`extensions`_, Mopidy can play music from cloud services like Spotify, +SoundCloud, and Google Play Music. +With Mopidy's extension support, backends for new music sources can be easily +added. + +**Mopidy is just a server** + +Mopidy is a Python application that runs in a terminal or in the background on +Linux computers or Macs that have network connectivity and audio output. +Out of the box, Mopidy is an HTTP server. If you install the `Mopidy-MPD`_ +extension, it becomes an MPD server too. Many additional frontends for +controlling Mopidy are available as extensions. + +**Pick your favorite client** + +You and the people around you can all connect their favorite MPD or web client +to the Mopidy server to search for music and manage the playlist together. +With a browser or MPD client, which is available for all popular operating +systems, you can control the music from any phone, tablet, or computer. + +**Mopidy on Raspberry Pi** + +The `Raspberry Pi`_ is an popular device to run Mopidy on, either using +Raspbian, Ubuntu, or Arch Linux. +Pimoroni recommends Mopidy for use with their `Pirate Audio`_ audio gear for +Raspberry Pi. +Mopidy is also a significant building block in the `Pi Musicbox`_ integrated +audio jukebox system for Raspberry Pi. + +**Mopidy is hackable** + +Mopidy's extension support and Python, JSON-RPC, and JavaScript APIs make +Mopidy a perfect base for your projects. +In one hack, a Raspberry Pi was embedded in an old cassette player. The buttons +and volume control are wired up with GPIO on the Raspberry Pi, and is used to +control playback through a custom Mopidy extension. The cassettes have NFC tags +used to select playlists from Spotify. + +.. _Mopidy: https://mopidy.com/ +.. _extensions: https://mopidy.com/ext/ +.. _Mopidy-MPD: https://mopidy.com/ext/mpd/ +.. _Raspberry Pi: https://www.raspberrypi.org/ +.. _Pirate Audio: https://shop.pimoroni.com/collections/pirate-audio +.. _Pi Musicbox: https://www.pimusicbox.com/ + + +**Getting started** + +To get started with Mopidy, begin by reading the +`installation docs `_. + + +**Project resources** + +- `Documentation `_ +- `Discourse forum `_ +- `Zulip chat `_ +- `Source code `_ +- `Issue tracker `_ + +.. image:: https://img.shields.io/pypi/v/Mopidy.svg?style=flat + :target: https://pypi.python.org/pypi/Mopidy/ + :alt: Latest PyPI version + +.. image:: https://img.shields.io/circleci/project/github/mopidy/mopidy/develop.svg + :target: https://circleci.com/gh/mopidy/mopidy + :alt: CircleCI build status + +.. image:: https://img.shields.io/readthedocs/mopidy.svg + :target: https://docs.mopidy.com/ + :alt: Read the Docs build status + +.. image:: https://img.shields.io/codecov/c/github/mopidy/mopidy/develop.svg + :target: https://codecov.io/gh/mopidy/mopidy + :alt: Test coverage + +.. image:: https://img.shields.io/badge/chat-on%20zulip-brightgreen + :target: https://mopidy.zulipchat.com/ + :alt: Chat on Zulip + + diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/RECORD b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/RECORD new file mode 100644 index 0000000..1584786 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/RECORD @@ -0,0 +1,153 @@ +../../../bin/mopidy,sha256=lgZfWjcBMUrNrU8miLCas-MntSyrQZqrMvndTp_Umn8,259 +Mopidy-3.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Mopidy-3.0.1.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +Mopidy-3.0.1.dist-info/METADATA,sha256=o8ZIC9I4ONgLjCPJX6G43wMf0ibG_IO1RpCbV9ZGVSc,6106 +Mopidy-3.0.1.dist-info/RECORD,, +Mopidy-3.0.1.dist-info/WHEEL,sha256=p46_5Uhzqz6AzeSosiOnxK-zmFja1i22CrQCjmYe8ec,92 +Mopidy-3.0.1.dist-info/entry_points.txt,sha256=gCC2QfPiw--VlywylJKwASW-PeDRQu7X9KhVenjOdY8,228 +Mopidy-3.0.1.dist-info/top_level.txt,sha256=wXGm4Xc_-v5CmPuhY4w4DsdVxJbTGRNeLOaylNYnCz4,7 +mopidy/__init__.py,sha256=zFBrPSHdCgbuiR61cJdEyd39aDuYNEnyJ2BJkpro2Sk,346 +mopidy/__main__.py,sha256=Ck1nUzYNtcgL8zBF2bKmWAB8ZcGeXpC3v7P5rOX5hkk,7881 +mopidy/__pycache__/__init__.cpython-37.pyc,, +mopidy/__pycache__/__main__.cpython-37.pyc,, +mopidy/__pycache__/backend.cpython-37.pyc,, +mopidy/__pycache__/commands.cpython-37.pyc,, +mopidy/__pycache__/exceptions.cpython-37.pyc,, +mopidy/__pycache__/ext.cpython-37.pyc,, +mopidy/__pycache__/httpclient.cpython-37.pyc,, +mopidy/__pycache__/listener.cpython-37.pyc,, +mopidy/__pycache__/local.cpython-37.pyc,, +mopidy/__pycache__/mixer.cpython-37.pyc,, +mopidy/__pycache__/zeroconf.cpython-37.pyc,, +mopidy/audio/__init__.py,sha256=iwvzpaqam0vd5PoTYMYjsJsw4Nli1drElayzRpsMzJY,236 +mopidy/audio/__pycache__/__init__.cpython-37.pyc,, +mopidy/audio/__pycache__/actor.cpython-37.pyc,, +mopidy/audio/__pycache__/constants.cpython-37.pyc,, +mopidy/audio/__pycache__/listener.cpython-37.pyc,, +mopidy/audio/__pycache__/scan.cpython-37.pyc,, +mopidy/audio/__pycache__/tags.cpython-37.pyc,, +mopidy/audio/__pycache__/utils.cpython-37.pyc,, +mopidy/audio/actor.py,sha256=GbToLnN7qo-54raJ9QLSRQm183NjEx5rM9uuQ4iDd4Y,30330 +mopidy/audio/constants.py,sha256=O_NbhefMHRsY7mY6VlmfBtGKy9iO3c5ibiqzAuLzay8,283 +mopidy/audio/listener.py,sha256=dwDHgE5vdorGmEDBBk2HzUNB6FAgP_ZUnb19g1YS0Fk,3210 +mopidy/audio/scan.py,sha256=fh5Je1gNTL95eA4xAh7eMvGYqajnGpEbIx6-4l5cmbQ,10564 +mopidy/audio/tags.py,sha256=r9uL5G6dv7_I9fJjqZEJBJa3KW7ziKaBobei2tgZ3ao,5795 +mopidy/audio/utils.py,sha256=qgSlXdNM38zmzVUsNnrhLjfGICzCTnm2dCxGfmZ-F1Y,3226 +mopidy/backend.py,sha256=FX09s58SreKFNYtxwjqkUv1AogmEWI1WnAFwux30xhI,12654 +mopidy/commands.py,sha256=ax9rkKm5A4d_hoMjWBkaHHpWlkI9UaIvYY4YNzHFdMg,15807 +mopidy/config/__init__.py,sha256=9Fd-BpZ11MvxSsyx6wAxPhpvh_fD2ii4UJX2h7zg-Uk,10282 +mopidy/config/__pycache__/__init__.cpython-37.pyc,, +mopidy/config/__pycache__/keyring.cpython-37.pyc,, +mopidy/config/__pycache__/schemas.cpython-37.pyc,, +mopidy/config/__pycache__/types.cpython-37.pyc,, +mopidy/config/__pycache__/validators.cpython-37.pyc,, +mopidy/config/default.conf,sha256=Bob7NC6wKALozG_K30VroPdWKU_InnzLjQMv8DmdLoM,438 +mopidy/config/keyring.py,sha256=emA1FFQo4cutNC9j-1-m8rf-k5TnLczaOSbBzQDH3EA,5467 +mopidy/config/schemas.py,sha256=otdlIi4HgJ7mvmz9prdQmluWSmDEqBOyXbaxd6yw0NI,3957 +mopidy/config/types.py,sha256=U_J9bgj36Sgy_TeFLhBOtlYwqcRn0N044TGjISQcMX4,9135 +mopidy/config/validators.py,sha256=FlpiCUVEwsUHLCfygc1q2ujkTUUbCzwYMqakJf_aWpM,1287 +mopidy/core/__init__.py,sha256=fkawmhO1kHIRWjpXceaCrKZRyDLcSay50dzjcAp4FZ8,329 +mopidy/core/__pycache__/__init__.cpython-37.pyc,, +mopidy/core/__pycache__/actor.cpython-37.pyc,, +mopidy/core/__pycache__/history.cpython-37.pyc,, +mopidy/core/__pycache__/library.cpython-37.pyc,, +mopidy/core/__pycache__/listener.cpython-37.pyc,, +mopidy/core/__pycache__/mixer.cpython-37.pyc,, +mopidy/core/__pycache__/playback.cpython-37.pyc,, +mopidy/core/__pycache__/playlists.cpython-37.pyc,, +mopidy/core/__pycache__/tracklist.cpython-37.pyc,, +mopidy/core/actor.py,sha256=da2UmYiP8CJ2e_v05l8UH7fWErp4r9ZJ8Rux0mJgON8,9859 +mopidy/core/history.py,sha256=qiDZvBs2T7k-VBrB3WXjqQRiX3_BgGPoKFcyzzvgldI,2152 +mopidy/core/library.py,sha256=ZdEs6OT3m15JtMP9gqPOy21WJydLUdJclG-tfWCrZ-k,12647 +mopidy/core/listener.py,sha256=k6nYzHvCOxediKC-W5vaeNdZEKYjyuDLc-fwW-B8DQw,5207 +mopidy/core/mixer.py,sha256=CVI6_3_57W4d3PVU8pQ-KDZpMWZpJSj6CzOrG0umxrg,3012 +mopidy/core/playback.py,sha256=_SHbNMyA3z8Ncwt_j2jXPvXhMIIFurqL_bBWO02vYTg,20255 +mopidy/core/playlists.py,sha256=6tWn5n4FMe8F3aDnF-YudlJuJTylczUJPcJR5BWsHrw,9501 +mopidy/core/tracklist.py,sha256=d1nBY126yfPSa1r0ixATMwS7w7cq49Fuy79duP8Rt8I,20874 +mopidy/exceptions.py,sha256=bDQf4jT7WFQe0wj7LawJLRntcs-Pn0MJutNQgSkSWQw,1034 +mopidy/ext.py,sha256=a4yxuZqVf6zztF9JfEFGsnpouAe7ehq1yLb29ufFWlE,10226 +mopidy/file/__init__.py,sha256=cRwousGVqkDBqqCWFVuvkygpkVd7bNniJnFHtg_3C_k,921 +mopidy/file/__pycache__/__init__.cpython-37.pyc,, +mopidy/file/__pycache__/backend.cpython-37.pyc,, +mopidy/file/__pycache__/library.cpython-37.pyc,, +mopidy/file/backend.py,sha256=STv-7j0SDw1IhYxu6MP8Ku2nSadTvTNzL0yQwnqArsU,467 +mopidy/file/ext.conf,sha256=mpMlZb_5Tbd09R_SGHCKIZ8OllnXkLwviZdeRBvEdag,247 +mopidy/file/library.py,sha256=QjgawImUI1SiKqB0psI15wwgK_j1N6iZVSackuZIH2Q,4919 +mopidy/http/__init__.py,sha256=9MJBuSCI5vthoZ5EKnofYU74cpQbwP-67PSCgFJfId8,1668 +mopidy/http/__pycache__/__init__.cpython-37.pyc,, +mopidy/http/__pycache__/actor.cpython-37.pyc,, +mopidy/http/__pycache__/handlers.cpython-37.pyc,, +mopidy/http/actor.py,sha256=ymySZBMx4awimux3-ebkCld4Rcp-6teCiPJqRpio38U,6656 +mopidy/http/data/clients.html,sha256=GoiSK3myDwmBeICVssrXiX4sh2yN5tzjoLE7cyCJhzg,805 +mopidy/http/data/favicon.ico,sha256=hm3BaOU1gVvbifw6mXm0GsQFuy_m9N-JgLaQCox1LbQ,5997 +mopidy/http/data/mopidy.css,sha256=r3AH86Za-ptEMX_1t_VwbwT6qtAV1InVj__DBQEj_Zk,610 +mopidy/http/ext.conf,sha256=xgoLEmntOaWm-ICI3OhKSiyvXYK---RtSPqAWBrkNSc,160 +mopidy/http/handlers.py,sha256=Zn0Uz1LUavqV77kLoeCSH4L40iYwGY5LPOysJmI0NtU,9012 +mopidy/httpclient.py,sha256=0ulap7HNpGgAwPKJw_qGIOTg1Arc2-pJr6ncxw03gc4,1458 +mopidy/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mopidy/internal/__pycache__/__init__.cpython-37.pyc,, +mopidy/internal/__pycache__/deprecation.cpython-37.pyc,, +mopidy/internal/__pycache__/deps.cpython-37.pyc,, +mopidy/internal/__pycache__/formatting.cpython-37.pyc,, +mopidy/internal/__pycache__/gi.cpython-37.pyc,, +mopidy/internal/__pycache__/http.cpython-37.pyc,, +mopidy/internal/__pycache__/jsonrpc.cpython-37.pyc,, +mopidy/internal/__pycache__/log.cpython-37.pyc,, +mopidy/internal/__pycache__/models.cpython-37.pyc,, +mopidy/internal/__pycache__/network.cpython-37.pyc,, +mopidy/internal/__pycache__/path.cpython-37.pyc,, +mopidy/internal/__pycache__/playlists.cpython-37.pyc,, +mopidy/internal/__pycache__/process.cpython-37.pyc,, +mopidy/internal/__pycache__/storage.cpython-37.pyc,, +mopidy/internal/__pycache__/timer.cpython-37.pyc,, +mopidy/internal/__pycache__/validation.cpython-37.pyc,, +mopidy/internal/__pycache__/versioning.cpython-37.pyc,, +mopidy/internal/__pycache__/xdg.cpython-37.pyc,, +mopidy/internal/deprecation.py,sha256=neGteglaaWpBLqy8IpS67RXEPkuaMjA-xzPiOSOV-Qw,1588 +mopidy/internal/deps.py,sha256=vWGUgQfU6Ny-jym9I3lDuRwtlUOUZwYzRSTQoT_FX8Q,4957 +mopidy/internal/formatting.py,sha256=ezTDGsLvXH2q1hH3teLVE1E_afHmU-OdANB40bCry9Y,858 +mopidy/internal/gi.py,sha256=t1m5bECCsup7v-39I3JP-v_xQ-LGlOeSB2tWMoU8UqI,1166 +mopidy/internal/http.py,sha256=yfbP7st9GUCIMTEvbwRA4kLld8uPn2k-R4Waj5Ymj_U,1681 +mopidy/internal/jsonrpc.py,sha256=91bRCIWe1Fkn8leoRZxGfgK45_TEcenhmM_JgScl11s,12327 +mopidy/internal/log.py,sha256=zkHUzUzpuZAmJiz9bsy7ZXNB7svXbgIgGYRE7nfyt44,6073 +mopidy/internal/models.py,sha256=kJo6fx9Bj_K6j6A0MnXor-xjBYZV7yqG4-2Lx2z5HRE,3745 +mopidy/internal/network.py,sha256=LU9YgBXyuYVf7mGC-9O6XOIPN1F4wSeGnyjQGC--4I4,762 +mopidy/internal/path.py,sha256=HEJVVicbT_HUkO1tNgc3q3ktw5TwzaoGdOiCPKY2RNI,2879 +mopidy/internal/playlists.py,sha256=fydVOfj35TeOBrEg0DWJOe7Aqmbv4R-a8OiMgDXkQvs,3478 +mopidy/internal/process.py,sha256=YqG2xQAXPtk42DhM9a80nY9owal7YYUVdwdiT109lx0,1583 +mopidy/internal/storage.py,sha256=KBkZg-SwUU_VCg45a-8kSN8bIEQ184ikBl2c1yVEFC0,1460 +mopidy/internal/timer.py,sha256=fkgkqSmpWEwbum4isGM2ssIdbRnH8kV9V20vO0vTW7w,303 +mopidy/internal/validation.py,sha256=E3QNBaVw2h_LfGVk2hspqtp89IMhgi846Sqfeh--Xsk,3965 +mopidy/internal/versioning.py,sha256=ql7cfMIqo-_ONVA0ATBFFAjwsxtNK3EY6Ewe-9L0eF8,665 +mopidy/internal/xdg.py,sha256=vCvTcqLTIMu8cDd5maWq4xKK-kfKzg48wMiDv805dYM,2053 +mopidy/listener.py,sha256=0e1qI2pobjrM-q-mXMvgeXkJTcyTGvo43QTGzSe9-ns,1578 +mopidy/local.py,sha256=b39mPnq_CKgDCGF12fpZEiJnQUz9jRW2Ul25mPUlr7A,431 +mopidy/m3u/__init__.py,sha256=vHaP2oJgFx3H8zOpXTsoY0z4ZqNxOXMavPuQOOTDntg,839 +mopidy/m3u/__pycache__/__init__.cpython-37.pyc,, +mopidy/m3u/__pycache__/backend.cpython-37.pyc,, +mopidy/m3u/__pycache__/playlists.cpython-37.pyc,, +mopidy/m3u/__pycache__/translator.cpython-37.pyc,, +mopidy/m3u/backend.py,sha256=FKfcB5tM3924In_ZWSuz_hDmYPn6vLdVpTpuD3awTbg,288 +mopidy/m3u/ext.conf,sha256=20FM6epg_yhoIoI9nsJLAuDpXIZjLRk_DjzPXEaxMl8,116 +mopidy/m3u/playlists.py,sha256=mN0aRT6gRs-ISaFCzdjucqsKMUO3_4SiO_v_HjHy2Gc,5960 +mopidy/m3u/translator.py,sha256=WmzxJlDM_1hgF40MIVKvX0_70OkSHPAlmBq30yqN82Q,2464 +mopidy/mixer.py,sha256=Qpt0_5PFw1FhXvRVfW7R--iYwUoNOIVjqoE0O5GGp54,4156 +mopidy/models/__init__.py,sha256=nfIiAQRu42z1UEP4PvmP5Pp26mqmnSzX8uCCTE8gZjo,10417 +mopidy/models/__pycache__/__init__.cpython-37.pyc,, +mopidy/models/__pycache__/fields.cpython-37.pyc,, +mopidy/models/__pycache__/immutable.cpython-37.pyc,, +mopidy/models/__pycache__/serialize.cpython-37.pyc,, +mopidy/models/fields.py,sha256=DJ7QWR_82UxW967J5nuLjPkwNnuC6GdY32p1IqVmCQo,5281 +mopidy/models/immutable.py,sha256=USfNjCBDu3jZyOemR99zoXBRlrC2bEHZpz8qdX8Z-7I,7076 +mopidy/models/serialize.py,sha256=rUSjY8tOmJkQwQDTusX1GEYYjs2zTNWFnjj9xMJa4Zg,1054 +mopidy/softwaremixer/__init__.py,sha256=pjmfTdI0SKGBwHayIJceBfphElOVP--ef7N8Mm-g3dM,559 +mopidy/softwaremixer/__pycache__/__init__.cpython-37.pyc,, +mopidy/softwaremixer/__pycache__/mixer.cpython-37.pyc,, +mopidy/softwaremixer/ext.conf,sha256=nca74OmIqnsY32f6JIUQk2KVDfI3wqT4BCWjMILLq_M,31 +mopidy/softwaremixer/mixer.py,sha256=-nVWKWLw52xW4b4r6-_dgfkVdE8pntdlBnF4afx50sU,1599 +mopidy/stream/__init__.py,sha256=yZ66Ck3uGKSBfPnRa3uq7TRAVb0XowgAFUMqAbEv-QM,788 +mopidy/stream/__pycache__/__init__.cpython-37.pyc,, +mopidy/stream/__pycache__/actor.cpython-37.pyc,, +mopidy/stream/actor.py,sha256=Aa3D3DEYVQLaMmp9Hju-6_sUz171nnyLXnqbwx8t524,5982 +mopidy/stream/ext.conf,sha256=RjLTZNsyYLIkV0-4RNAbInbEIg2yMNlzJNWgtg5dP8U,127 +mopidy/zeroconf.py,sha256=T38d3Zg9F80UECpT_wgzW10wycNJrgVXZQoE6GgbUzU,4437 diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/WHEEL b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/WHEEL new file mode 100644 index 0000000..3b5c403 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.6) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/entry_points.txt b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/entry_points.txt new file mode 100644 index 0000000..4a9520a --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/entry_points.txt @@ -0,0 +1,10 @@ +[console_scripts] +mopidy = mopidy.__main__:main + +[mopidy.ext] +file = mopidy.file:Extension +http = mopidy.http:Extension +m3u = mopidy.m3u:Extension +softwaremixer = mopidy.softwaremixer:Extension +stream = mopidy.stream:Extension + diff --git a/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/top_level.txt new file mode 100644 index 0000000..1cfd43c --- /dev/null +++ b/venv/lib/python3.7/site-packages/Mopidy-3.0.1.dist-info/top_level.txt @@ -0,0 +1 @@ +mopidy diff --git a/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/LICENSE b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/METADATA b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/METADATA new file mode 100644 index 0000000..c24022a --- /dev/null +++ b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/METADATA @@ -0,0 +1,89 @@ +Metadata-Version: 2.1 +Name: Pykka +Version: 2.0.2 +Summary: Pykka is a Python implementation of the actor model +Home-page: https://www.pykka.org/ +Author: Stein Magnus Jodal +Author-email: stein.magnus@jodal.no +License: Apache License, Version 2.0 +Project-URL: Issues, https://github.com/jodal/pykka/issues +Project-URL: Source, https://github.com/jodal/pykka +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Provides-Extra: dev +Requires-Dist: black ; extra == 'dev' +Requires-Dist: check-manifest ; extra == 'dev' +Requires-Dist: flake8 ; extra == 'dev' +Requires-Dist: flake8-import-order ; extra == 'dev' +Requires-Dist: pytest ; extra == 'dev' +Requires-Dist: pytest-cov ; extra == 'dev' +Requires-Dist: pytest-mock ; extra == 'dev' +Requires-Dist: sphinx ; extra == 'dev' +Requires-Dist: sphinx-rtd-theme ; extra == 'dev' +Requires-Dist: tox ; extra == 'dev' +Requires-Dist: twine ; extra == 'dev' +Requires-Dist: wheel ; extra == 'dev' + +===== +Pykka +===== + +Pykka is a Python implementation of the `actor model +`_. The actor model introduces some +simple rules to control the sharing of state and cooperation between execution +units, which makes it easier to build concurrent applications. + +For details and code examples, see the `Pykka documentation +`_. + +Pykka is available from PyPI. To install it, run:: + + pip install pykka + +Pykka works with CPython 2.7 and 3.5+, as well as PyPy 2.7 and 3.5+. + + +License +======= + +Pykka is copyright 2010-2019 Stein Magnus Jodal and contributors. +Pykka is licensed under the `Apache License, Version 2.0 +`_. + + +Project resources +================= + +- `Documentation `_ +- `Source code `_ +- `Issue tracker `_ + +.. image:: https://img.shields.io/pypi/v/Pykka.svg + :target: https://pypi.org/project/Pykka/ + :alt: Latest PyPI version + +.. image:: https://img.shields.io/circleci/project/github/jodal/pykka/develop.svg + :target: https://circleci.com/gh/jodal/pykka + :alt: CircleCI build status + +.. image:: https://img.shields.io/readthedocs/pykka.svg + :target: https://www.pykka.org/ + :alt: Read the Docs build status + +.. image:: https://img.shields.io/codecov/c/github/jodal/pykka/develop.svg + :target: https://codecov.io/gh/jodal/pykka + :alt: Test coverage + + diff --git a/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/RECORD b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/RECORD new file mode 100644 index 0000000..4b5a1b2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/RECORD @@ -0,0 +1,36 @@ +Pykka-2.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Pykka-2.0.2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +Pykka-2.0.2.dist-info/METADATA,sha256=121HFgIU2PAjqGj4z8-UmK0yQ_2qVFvXeRn-ZVQ2IsU,3099 +Pykka-2.0.2.dist-info/RECORD,, +Pykka-2.0.2.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110 +Pykka-2.0.2.dist-info/top_level.txt,sha256=Xf5RIn3WblO67T1uO2zFkPA12CFNMlUzevb5eNqrc_E,6 +pykka/__init__.py,sha256=hLvsSdf1cHFEKHmKgySzUmGZuaRw0-XuLst-3uF5H8o,772 +pykka/__pycache__/__init__.cpython-37.pyc,, +pykka/__pycache__/_actor.cpython-37.pyc,, +pykka/__pycache__/_envelope.cpython-37.pyc,, +pykka/__pycache__/_exceptions.cpython-37.pyc,, +pykka/__pycache__/_future.cpython-37.pyc,, +pykka/__pycache__/_proxy.cpython-37.pyc,, +pykka/__pycache__/_ref.cpython-37.pyc,, +pykka/__pycache__/_registry.cpython-37.pyc,, +pykka/__pycache__/_threading.cpython-37.pyc,, +pykka/__pycache__/debug.cpython-37.pyc,, +pykka/__pycache__/eventlet.cpython-37.pyc,, +pykka/__pycache__/gevent.cpython-37.pyc,, +pykka/__pycache__/messages.cpython-37.pyc,, +pykka/_actor.py,sha256=uij2jPyOV0LmCN8wmTqPcOPkJxB00pWMYGx_gpRdAHU,12388 +pykka/_compat/__init__.py,sha256=4SVjOsf7daueyJWZsbK4FyQGDlidF_DADrMLQoB6m1c,870 +pykka/_compat/__pycache__/__init__.cpython-37.pyc,, +pykka/_compat/__pycache__/await_py3.cpython-37.pyc,, +pykka/_compat/await_py3.py,sha256=YGTNqd17xP9Z5zbhXnugToxDnnzlWC9coLtkQKh6YSY,134 +pykka/_envelope.py,sha256=ZzMVFoHO-Hon5WUkyIEufdAWz5JQnmbCM0FRP7Jws00,669 +pykka/_exceptions.py,sha256=iMwfs6ksm3Q7Rtp2YRFqD3446wr-BCGbPpwj9dbAEiI,244 +pykka/_future.py,sha256=gmyFODrnbiBIntk34FHqhVje4J2_koRyx6UAJsZAHE8,8592 +pykka/_proxy.py,sha256=VzZdDjsyfmOQTNXicrlpr7HIM3c2uxlS1Yva_Q7MDMY,11938 +pykka/_ref.py,sha256=CSDPAoOEYqXySbaqF_83TMNqmTzUkiMaCZFj200cuuc,5423 +pykka/_registry.py,sha256=4aUbla_aSyX8QSBYs_9B7z-xVCqgUwgkrdS_WCQMG2M,5335 +pykka/_threading.py,sha256=qJq553IbG3yzVmZ9F3K_jNydAoJth7kVNvO2QWwgWDU,3248 +pykka/debug.py,sha256=MJdGsnAirUXP2gxh1f4QWQSPEtNIzdND1Uu5K9yJAdc,2262 +pykka/eventlet.py,sha256=r7sxEswMGVutyTy-k9qYCh3SfSQnjM2Yz4Hhr8hNXCA,2572 +pykka/gevent.py,sha256=8HqYD7p4UJZsmCIvxmm0KnQzRAyHwwr0u7zNnmUymZk,2036 +pykka/messages.py,sha256=B55XhrPAf3Fti5hDc7jFcGlwtr-u0e201EAjXyorCxo,2562 diff --git a/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/WHEEL b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/WHEEL new file mode 100644 index 0000000..8b701e9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.6) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/top_level.txt new file mode 100644 index 0000000..85e0d3e --- /dev/null +++ b/venv/lib/python3.7/site-packages/Pykka-2.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +pykka diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/DESCRIPTION.rst b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..0b0953d --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/DESCRIPTION.rst @@ -0,0 +1,50 @@ +Certifi: Python SSL Certificates +================================ + +`Certifi`_ is a carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python2.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python2.7/site-packages/certifi/cacert.pem + +Enjoy! + +1024-bit Root Certificates +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Browsers and certificate authorities have concluded that 1024-bit keys are +unacceptably weak for certificates, particularly root certificates. For this +reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its +bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) +certificate from the same CA. Because Mozilla removed these certificates from +its bundle, ``certifi`` removed them as well. + +In previous versions, ``certifi`` provided the ``certifi.old_where()`` function +to intentionally re-add the 1024-bit roots back into your bundle. This was not +recommended in production and therefore was removed at the end of 2018. + +.. _`Certifi`: https://certifi.io/en/latest/ +.. _`Requests`: http://docs.python-requests.org/en/latest/ + + diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/METADATA b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/METADATA new file mode 100644 index 0000000..bc0532f --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/METADATA @@ -0,0 +1,74 @@ +Metadata-Version: 2.0 +Name: certifi +Version: 2019.11.28 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://certifi.io/ +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 + +Certifi: Python SSL Certificates +================================ + +`Certifi`_ is a carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python2.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python2.7/site-packages/certifi/cacert.pem + +Enjoy! + +1024-bit Root Certificates +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Browsers and certificate authorities have concluded that 1024-bit keys are +unacceptably weak for certificates, particularly root certificates. For this +reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its +bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) +certificate from the same CA. Because Mozilla removed these certificates from +its bundle, ``certifi`` removed them as well. + +In previous versions, ``certifi`` provided the ``certifi.old_where()`` function +to intentionally re-add the 1024-bit roots back into your bundle. This was not +recommended in production and therefore was removed at the end of 2018. + +.. _`Certifi`: https://certifi.io/en/latest/ +.. _`Requests`: http://docs.python-requests.org/en/latest/ + + diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/RECORD b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/RECORD new file mode 100644 index 0000000..20931bd --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2019.11.28.dist-info/DESCRIPTION.rst,sha256=aLNHONztn2ZiBpSTivVFy6EDIWmuNYSsEQwx4NWbvB4,1580 +certifi-2019.11.28.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2019.11.28.dist-info/METADATA,sha256=CnYsfjDpEJJMNgBGSD2v_WN-PS-g4ZmIt1aiZ8UiRiE,2523 +certifi-2019.11.28.dist-info/RECORD,, +certifi-2019.11.28.dist-info/WHEEL,sha256=5wvfB7GvgZAbKBSE9uX9Zbi6LCL-_KgezgHblXhCRnM,113 +certifi-2019.11.28.dist-info/metadata.json,sha256=9MSLVS0RruV3LnE_uHbsv6QHamn7Lq9GwQ_gZOrw4Mw,1023 +certifi-2019.11.28.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=JVwzDhkMttyVVtfNDrU_i0v2a-WmtEBXq0Z8oz4Ghzk,52 +certifi/__main__.py,sha256=FiOYt1Fltst7wk9DRa6GCoBr8qBUxlNQu_MKJf04E6s,41 +certifi/__pycache__/__init__.cpython-37.pyc,, +certifi/__pycache__/__main__.cpython-37.pyc,, +certifi/__pycache__/core.cpython-37.pyc,, +certifi/cacert.pem,sha256=cyvv5Jx1gHACNEj2GaOrsIj0Tk8FmSvHR42uhzvlatg,281457 +certifi/core.py,sha256=EuFc2BsToG5O1-qsx4BSjQ1r1-7WRtH87b1WflZOWhI,218 diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/WHEEL b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/WHEEL new file mode 100644 index 0000000..7bf9daa --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0.a0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/metadata.json b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/metadata.json new file mode 100644 index 0000000..3a14841 --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "extensions": {"python.details": {"contacts": [{"email": "me@kennethreitz.com", "name": "Kenneth Reitz", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://certifi.io/"}}}, "generator": "bdist_wheel (0.30.0.a0)", "license": "MPL-2.0", "metadata_version": "2.0", "name": "certifi", "summary": "Python package for providing Mozilla's CA Bundle.", "version": "2019.11.28"} \ No newline at end of file diff --git a/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi-2019.11.28.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/venv/lib/python3.7/site-packages/certifi/__init__.py b/venv/lib/python3.7/site-packages/certifi/__init__.py new file mode 100644 index 0000000..0d59a05 --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi/__init__.py @@ -0,0 +1,3 @@ +from .core import where + +__version__ = "2019.11.28" diff --git a/venv/lib/python3.7/site-packages/certifi/__main__.py b/venv/lib/python3.7/site-packages/certifi/__main__.py new file mode 100644 index 0000000..5f1da0d --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi/__main__.py @@ -0,0 +1,2 @@ +from certifi import where +print(where()) diff --git a/venv/lib/python3.7/site-packages/certifi/cacert.pem b/venv/lib/python3.7/site-packages/certifi/cacert.pem new file mode 100644 index 0000000..a4758ef --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi/cacert.pem @@ -0,0 +1,4602 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +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=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----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" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +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" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE +BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu +IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow +RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY +U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv +Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br +YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF +nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH +6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt +eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ +c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ +MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH +HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf +jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 +5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB +rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c +wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB +AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp +WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 +xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ +2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ +IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 +aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X +em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR +dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ +OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ +hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy +tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +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" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +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" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----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: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +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+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----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" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +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" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----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" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Label: "LuxTrust Global Root 2" +# Serial: 59914338225734147123941058376788110305822489521 +# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c +# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f +# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +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" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +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" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +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 +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- diff --git a/venv/lib/python3.7/site-packages/certifi/core.py b/venv/lib/python3.7/site-packages/certifi/core.py new file mode 100644 index 0000000..7271acf --- /dev/null +++ b/venv/lib/python3.7/site-packages/certifi/core.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem. +""" +import os + + +def where(): + f = os.path.dirname(__file__) + + return os.path.join(f, 'cacert.pem') diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..c0f044d --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst @@ -0,0 +1,70 @@ +Chardet: The Universal Character Encoding Detector +-------------------------------------------------- + +.. image:: https://img.shields.io/travis/chardet/chardet/stable.svg + :alt: Build status + :target: https://travis-ci.org/chardet/chardet + +.. image:: https://img.shields.io/coveralls/chardet/chardet/stable.svg + :target: https://coveralls.io/r/chardet/chardet + +.. image:: https://img.shields.io/pypi/v/chardet.svg + :target: https://warehouse.python.org/project/chardet/ + :alt: Latest version on PyPI + +.. image:: https://img.shields.io/pypi/l/chardet.svg + :alt: License + + +Detects + - ASCII, UTF-8, UTF-16 (2 variants), UTF-32 (4 variants) + - Big5, GB2312, EUC-TW, HZ-GB-2312, ISO-2022-CN (Traditional and Simplified Chinese) + - EUC-JP, SHIFT_JIS, CP932, ISO-2022-JP (Japanese) + - EUC-KR, ISO-2022-KR (Korean) + - KOI8-R, MacCyrillic, IBM855, IBM866, ISO-8859-5, windows-1251 (Cyrillic) + - ISO-8859-5, windows-1251 (Bulgarian) + - ISO-8859-1, windows-1252 (Western European languages) + - ISO-8859-7, windows-1253 (Greek) + - ISO-8859-8, windows-1255 (Visual and Logical Hebrew) + - TIS-620 (Thai) + +.. note:: + Our ISO-8859-2 and windows-1250 (Hungarian) probers have been temporarily + disabled until we can retrain the models. + +Requires Python 2.6, 2.7, or 3.3+. + +Installation +------------ + +Install from `PyPI `_:: + + pip install chardet + +Documentation +------------- + +For users, docs are now available at https://chardet.readthedocs.io/. + +Command-line Tool +----------------- + +chardet comes with a command-line script which reports on the encodings of one +or more files:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +About +----- + +This is a continuation of Mark Pilgrim's excellent chardet. Previously, two +versions needed to be maintained: one that supported python 2.x and one that +supported python 3.x. We've recently merged with `Ian Cordasco `_'s +`charade `_ fork, so now we have one +coherent version that works for Python 2.6+. + +:maintainer: Dan Blanchard + + diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/METADATA b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/METADATA new file mode 100644 index 0000000..1427867 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/METADATA @@ -0,0 +1,96 @@ +Metadata-Version: 2.0 +Name: chardet +Version: 3.0.4 +Summary: Universal encoding detector for Python 2 and 3 +Home-page: https://github.com/chardet/chardet +Author: Daniel Blanchard +Author-email: dan.blanchard@gmail.com +License: LGPL +Keywords: encoding,i18n,xml +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Linguistic + +Chardet: The Universal Character Encoding Detector +-------------------------------------------------- + +.. image:: https://img.shields.io/travis/chardet/chardet/stable.svg + :alt: Build status + :target: https://travis-ci.org/chardet/chardet + +.. image:: https://img.shields.io/coveralls/chardet/chardet/stable.svg + :target: https://coveralls.io/r/chardet/chardet + +.. image:: https://img.shields.io/pypi/v/chardet.svg + :target: https://warehouse.python.org/project/chardet/ + :alt: Latest version on PyPI + +.. image:: https://img.shields.io/pypi/l/chardet.svg + :alt: License + + +Detects + - ASCII, UTF-8, UTF-16 (2 variants), UTF-32 (4 variants) + - Big5, GB2312, EUC-TW, HZ-GB-2312, ISO-2022-CN (Traditional and Simplified Chinese) + - EUC-JP, SHIFT_JIS, CP932, ISO-2022-JP (Japanese) + - EUC-KR, ISO-2022-KR (Korean) + - KOI8-R, MacCyrillic, IBM855, IBM866, ISO-8859-5, windows-1251 (Cyrillic) + - ISO-8859-5, windows-1251 (Bulgarian) + - ISO-8859-1, windows-1252 (Western European languages) + - ISO-8859-7, windows-1253 (Greek) + - ISO-8859-8, windows-1255 (Visual and Logical Hebrew) + - TIS-620 (Thai) + +.. note:: + Our ISO-8859-2 and windows-1250 (Hungarian) probers have been temporarily + disabled until we can retrain the models. + +Requires Python 2.6, 2.7, or 3.3+. + +Installation +------------ + +Install from `PyPI `_:: + + pip install chardet + +Documentation +------------- + +For users, docs are now available at https://chardet.readthedocs.io/. + +Command-line Tool +----------------- + +chardet comes with a command-line script which reports on the encodings of one +or more files:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +About +----- + +This is a continuation of Mark Pilgrim's excellent chardet. Previously, two +versions needed to be maintained: one that supported python 2.x and one that +supported python 3.x. We've recently merged with `Ian Cordasco `_'s +`charade `_ fork, so now we have one +coherent version that works for Python 2.6+. + +:maintainer: Dan Blanchard + + diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/RECORD b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/RECORD new file mode 100644 index 0000000..2740f3f --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/RECORD @@ -0,0 +1,91 @@ +../../../bin/chardetect,sha256=jUNQauPaQnX5_WVyQ21crfNyR7-as49GxQ5AYwGcevc,266 +chardet-3.0.4.dist-info/DESCRIPTION.rst,sha256=PQ4sBsMyKFZkjC6QpmbpLn0UtCNyeb-ZqvCGEgyZMGk,2174 +chardet-3.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +chardet-3.0.4.dist-info/METADATA,sha256=RV_2I4B1Z586DL8oVO5Kp7X5bUdQ5EuKAvNoAEF8wSw,3239 +chardet-3.0.4.dist-info/RECORD,, +chardet-3.0.4.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110 +chardet-3.0.4.dist-info/entry_points.txt,sha256=fAMmhu5eJ-zAJ-smfqQwRClQ3-nozOCmvJ6-E8lgGJo,60 +chardet-3.0.4.dist-info/metadata.json,sha256=0htbRM18ujyGZDdfowgAqj6Hq2eQtwzwyhaEveKntgo,1375 +chardet-3.0.4.dist-info/top_level.txt,sha256=AowzBbZy4x8EirABDdJSLJZMkJ_53iIag8xfKR6D7kI,8 +chardet/__init__.py,sha256=YsP5wQlsHJ2auF1RZJfypiSrCA7_bQiRm3ES_NI76-Y,1559 +chardet/__pycache__/__init__.cpython-37.pyc,, +chardet/__pycache__/big5freq.cpython-37.pyc,, +chardet/__pycache__/big5prober.cpython-37.pyc,, +chardet/__pycache__/chardistribution.cpython-37.pyc,, +chardet/__pycache__/charsetgroupprober.cpython-37.pyc,, +chardet/__pycache__/charsetprober.cpython-37.pyc,, +chardet/__pycache__/codingstatemachine.cpython-37.pyc,, +chardet/__pycache__/compat.cpython-37.pyc,, +chardet/__pycache__/cp949prober.cpython-37.pyc,, +chardet/__pycache__/enums.cpython-37.pyc,, +chardet/__pycache__/escprober.cpython-37.pyc,, +chardet/__pycache__/escsm.cpython-37.pyc,, +chardet/__pycache__/eucjpprober.cpython-37.pyc,, +chardet/__pycache__/euckrfreq.cpython-37.pyc,, +chardet/__pycache__/euckrprober.cpython-37.pyc,, +chardet/__pycache__/euctwfreq.cpython-37.pyc,, +chardet/__pycache__/euctwprober.cpython-37.pyc,, +chardet/__pycache__/gb2312freq.cpython-37.pyc,, +chardet/__pycache__/gb2312prober.cpython-37.pyc,, +chardet/__pycache__/hebrewprober.cpython-37.pyc,, +chardet/__pycache__/jisfreq.cpython-37.pyc,, +chardet/__pycache__/jpcntx.cpython-37.pyc,, +chardet/__pycache__/langbulgarianmodel.cpython-37.pyc,, +chardet/__pycache__/langcyrillicmodel.cpython-37.pyc,, +chardet/__pycache__/langgreekmodel.cpython-37.pyc,, +chardet/__pycache__/langhebrewmodel.cpython-37.pyc,, +chardet/__pycache__/langhungarianmodel.cpython-37.pyc,, +chardet/__pycache__/langthaimodel.cpython-37.pyc,, +chardet/__pycache__/langturkishmodel.cpython-37.pyc,, +chardet/__pycache__/latin1prober.cpython-37.pyc,, +chardet/__pycache__/mbcharsetprober.cpython-37.pyc,, +chardet/__pycache__/mbcsgroupprober.cpython-37.pyc,, +chardet/__pycache__/mbcssm.cpython-37.pyc,, +chardet/__pycache__/sbcharsetprober.cpython-37.pyc,, +chardet/__pycache__/sbcsgroupprober.cpython-37.pyc,, +chardet/__pycache__/sjisprober.cpython-37.pyc,, +chardet/__pycache__/universaldetector.cpython-37.pyc,, +chardet/__pycache__/utf8prober.cpython-37.pyc,, +chardet/__pycache__/version.cpython-37.pyc,, +chardet/big5freq.py,sha256=D_zK5GyzoVsRes0HkLJziltFQX0bKCLOrFe9_xDvO_8,31254 +chardet/big5prober.py,sha256=kBxHbdetBpPe7xrlb-e990iot64g_eGSLd32lB7_h3M,1757 +chardet/chardistribution.py,sha256=3woWS62KrGooKyqz4zQSnjFbJpa6V7g02daAibTwcl8,9411 +chardet/charsetgroupprober.py,sha256=6bDu8YIiRuScX4ca9Igb0U69TA2PGXXDej6Cc4_9kO4,3787 +chardet/charsetprober.py,sha256=KSmwJErjypyj0bRZmC5F5eM7c8YQgLYIjZXintZNstg,5110 +chardet/cli/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +chardet/cli/__pycache__/__init__.cpython-37.pyc,, +chardet/cli/__pycache__/chardetect.cpython-37.pyc,, +chardet/cli/chardetect.py,sha256=YBO8L4mXo0WR6_-Fjh_8QxPBoEBNqB9oNxNrdc54AQs,2738 +chardet/codingstatemachine.py,sha256=VYp_6cyyki5sHgXDSZnXW4q1oelHc3cu9AyQTX7uug8,3590 +chardet/compat.py,sha256=PKTzHkSbtbHDqS9PyujMbX74q1a8mMpeQTDVsQhZMRw,1134 +chardet/cp949prober.py,sha256=TZ434QX8zzBsnUvL_8wm4AQVTZ2ZkqEEQL_lNw9f9ow,1855 +chardet/enums.py,sha256=Aimwdb9as1dJKZaFNUH2OhWIVBVd6ZkJJ_WK5sNY8cU,1661 +chardet/escprober.py,sha256=kkyqVg1Yw3DIOAMJ2bdlyQgUFQhuHAW8dUGskToNWSc,3950 +chardet/escsm.py,sha256=RuXlgNvTIDarndvllNCk5WZBIpdCxQ0kcd9EAuxUh84,10510 +chardet/eucjpprober.py,sha256=iD8Jdp0ISRjgjiVN7f0e8xGeQJ5GM2oeZ1dA8nbSeUw,3749 +chardet/euckrfreq.py,sha256=-7GdmvgWez4-eO4SuXpa7tBiDi5vRXQ8WvdFAzVaSfo,13546 +chardet/euckrprober.py,sha256=MqFMTQXxW4HbzIpZ9lKDHB3GN8SP4yiHenTmf8g_PxY,1748 +chardet/euctwfreq.py,sha256=No1WyduFOgB5VITUA7PLyC5oJRNzRyMbBxaKI1l16MA,31621 +chardet/euctwprober.py,sha256=13p6EP4yRaxqnP4iHtxHOJ6R2zxHq1_m8hTRjzVZ95c,1747 +chardet/gb2312freq.py,sha256=JX8lsweKLmnCwmk8UHEQsLgkr_rP_kEbvivC4qPOrlc,20715 +chardet/gb2312prober.py,sha256=gGvIWi9WhDjE-xQXHvNIyrnLvEbMAYgyUSZ65HUfylw,1754 +chardet/hebrewprober.py,sha256=c3SZ-K7hvyzGY6JRAZxJgwJ_sUS9k0WYkvMY00YBYFo,13838 +chardet/jisfreq.py,sha256=vpmJv2Bu0J8gnMVRPHMFefTRvo_ha1mryLig8CBwgOg,25777 +chardet/jpcntx.py,sha256=PYlNqRUQT8LM3cT5FmHGP0iiscFlTWED92MALvBungo,19643 +chardet/langbulgarianmodel.py,sha256=1HqQS9Pbtnj1xQgxitJMvw8X6kKr5OockNCZWfEQrPE,12839 +chardet/langcyrillicmodel.py,sha256=LODajvsetH87yYDDQKA2CULXUH87tI223dhfjh9Zx9c,17948 +chardet/langgreekmodel.py,sha256=8YAW7bU8YwSJap0kIJSbPMw1BEqzGjWzqcqf0WgUKAA,12688 +chardet/langhebrewmodel.py,sha256=JSnqmE5E62tDLTPTvLpQsg5gOMO4PbdWRvV7Avkc0HA,11345 +chardet/langhungarianmodel.py,sha256=RhapYSG5l0ZaO-VV4Fan5sW0WRGQqhwBM61yx3yxyOA,12592 +chardet/langthaimodel.py,sha256=8l0173Gu_W6G8mxmQOTEF4ls2YdE7FxWf3QkSxEGXJQ,11290 +chardet/langturkishmodel.py,sha256=W22eRNJsqI6uWAfwXSKVWWnCerYqrI8dZQTm_M0lRFk,11102 +chardet/latin1prober.py,sha256=S2IoORhFk39FEFOlSFWtgVybRiP6h7BlLldHVclNkU8,5370 +chardet/mbcharsetprober.py,sha256=AR95eFH9vuqSfvLQZN-L5ijea25NOBCoXqw8s5O9xLQ,3413 +chardet/mbcsgroupprober.py,sha256=h6TRnnYq2OxG1WdD5JOyxcdVpn7dG0q-vB8nWr5mbh4,2012 +chardet/mbcssm.py,sha256=SY32wVIF3HzcjY3BaEspy9metbNSKxIIB0RKPn7tjpI,25481 +chardet/sbcharsetprober.py,sha256=LDSpCldDCFlYwUkGkwD2oFxLlPWIWXT09akH_2PiY74,5657 +chardet/sbcsgroupprober.py,sha256=1IprcCB_k1qfmnxGC6MBbxELlKqD3scW6S8YIwdeyXA,3546 +chardet/sjisprober.py,sha256=IIt-lZj0WJqK4rmUZzKZP4GJlE8KUEtFYVuY96ek5MQ,3774 +chardet/universaldetector.py,sha256=qL0174lSZE442eB21nnktT9_VcAye07laFWUeUrjttY,12485 +chardet/utf8prober.py,sha256=IdD8v3zWOsB8OLiyPi-y_fqwipRFxV9Nc1eKBLSuIEw,2766 +chardet/version.py,sha256=sp3B08mrDXB-pf3K9fqJ_zeDHOCLC8RrngQyDFap_7g,242 diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/WHEEL b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/WHEEL new file mode 100644 index 0000000..8b6dd1b --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt new file mode 100644 index 0000000..a884269 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +chardetect = chardet.cli.chardetect:main + diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/metadata.json b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/metadata.json new file mode 100644 index 0000000..8cdf025 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Linguistic"], "extensions": {"python.commands": {"wrap_console": {"chardetect": "chardet.cli.chardetect:main"}}, "python.details": {"contacts": [{"email": "dan.blanchard@gmail.com", "name": "Daniel Blanchard", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/chardet/chardet"}}, "python.exports": {"console_scripts": {"chardetect": "chardet.cli.chardetect:main"}}}, "generator": "bdist_wheel (0.29.0)", "keywords": ["encoding", "i18n", "xml"], "license": "LGPL", "metadata_version": "2.0", "name": "chardet", "summary": "Universal encoding detector for Python 2 and 3", "test_requires": [{"requires": ["hypothesis", "pytest"]}], "version": "3.0.4"} \ No newline at end of file diff --git a/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/top_level.txt new file mode 100644 index 0000000..79236f2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet-3.0.4.dist-info/top_level.txt @@ -0,0 +1 @@ +chardet diff --git a/venv/lib/python3.7/site-packages/chardet/__init__.py b/venv/lib/python3.7/site-packages/chardet/__init__.py new file mode 100644 index 0000000..0f9f820 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/__init__.py @@ -0,0 +1,39 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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 .compat import PY2, PY3 +from .universaldetector import UniversalDetector +from .version import __version__, VERSION + + +def detect(byte_str): + """ + Detect the encoding of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError('Expected object of type bytes or bytearray, got: ' + '{0}'.format(type(byte_str))) + else: + byte_str = bytearray(byte_str) + detector = UniversalDetector() + detector.feed(byte_str) + return detector.close() diff --git a/venv/lib/python3.7/site-packages/chardet/big5freq.py b/venv/lib/python3.7/site-packages/chardet/big5freq.py new file mode 100644 index 0000000..38f3251 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/big5freq.py @@ -0,0 +1,386 @@ +######################## 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 ######################### + +# Big5 frequency table +# by Taiwan's Mandarin Promotion Council +# +# +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +#Char to FreqOrder table +BIG5_TABLE_SIZE = 5376 + +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 +1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 + 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 +3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 +4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 +5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 + 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 + 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 + 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 +2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 +1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 +3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 + 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 +3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 +2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 + 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 +3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 +1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 +5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 + 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 +5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 +1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 + 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 + 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 +3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 +3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 + 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 +2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 +2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 + 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 + 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 +3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 +1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 +1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 +1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 +2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 + 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 +4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 +1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 +5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 +2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 + 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 + 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 + 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 + 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 +5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 + 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 +1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 + 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 + 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 +5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 +1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 + 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 +3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 +4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 +3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 + 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 + 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 +1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 +4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 +3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 +3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 +2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 +5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 +3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 +5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 +1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 +2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 +1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 + 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 +1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 +4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 +3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 + 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 + 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 + 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 +2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 +5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 +1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 +2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 +1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 +1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 +5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 +5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 +5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 +3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 +4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 +4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 +2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 +5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 +3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 + 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 +5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 +5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 +1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 +2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 +3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 +4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 +5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 +3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 +4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 +1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 +1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 +4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 +1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 + 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 +1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 +1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 +3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 + 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 +5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 +2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 +1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 +1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 +5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 + 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 +4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 + 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 +2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 + 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 +1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 +1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 + 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 +4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 +4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 +1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 +3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 +5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 +5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 +1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 +2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 +1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 +3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 +2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 +3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 +2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 +4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 +4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 +3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 + 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 +3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 + 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 +3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 +4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 +3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 +1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 +5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 + 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 +5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 +1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 + 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 +4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 +4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 + 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 +2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 +2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 +3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 +1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 +4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 +2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 +1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 +1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 +2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 +3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 +1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 +5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 +1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 +4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 +1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 + 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 +1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 +4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 +4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 +2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 +1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 +4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 + 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 +5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 +2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 +3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 +4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 + 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 +5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 +5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 +1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 +4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 +4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 +2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 +3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 +3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 +2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 +1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 +4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 +3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 +3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 +2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 +4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 +5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 +3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 +2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 +3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 +1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 +2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 +3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 +4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 +2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 +2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 +5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 +1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 +2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 +1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 +3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 +4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 +2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 +3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 +3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 +2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 +4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 +2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 +3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 +4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 +5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 +3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 + 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 +1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 +4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 +1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 +4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 +5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 + 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 +5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 +5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 +2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 +3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 +2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 +2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 + 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 +1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 +4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 +3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 +3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 + 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 +2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 + 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 +2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 +4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 +1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 +4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 +1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 +3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 + 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 +3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 +5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 +5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 +3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 +3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 +1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 +2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 +5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 +1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 +1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 +3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 + 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 +1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 +4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 +5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 +2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 +3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 + 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 +1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 +2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 +2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 +5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 +5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 +5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 +2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 +2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 +1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 +4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 +3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 +3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 +4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 +4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 +2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 +2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 +5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 +4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 +5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 +4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 + 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 + 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 +1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 +3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 +4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 +1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 +5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 +2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 +2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 +3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 +5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 +1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 +3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 +5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 +1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 +5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 +2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 +3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 +2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 +3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 +3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 +3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 +4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 + 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 +2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 +4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 +3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 +5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 +1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 +5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 + 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 +1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 + 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 +4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 +1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 +4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 +1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 + 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 +3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 +4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 +5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 + 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 +3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 + 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 +) + diff --git a/venv/lib/python3.7/site-packages/chardet/big5prober.py b/venv/lib/python3.7/site-packages/chardet/big5prober.py new file mode 100644 index 0000000..98f9970 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/big5prober.py @@ -0,0 +1,47 @@ +######################## 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import BIG5_SM_MODEL + + +class Big5Prober(MultiByteCharSetProber): + def __init__(self): + super(Big5Prober, self).__init__() + self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) + self.distribution_analyzer = Big5DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "Big5" + + @property + def language(self): + return "Chinese" diff --git a/venv/lib/python3.7/site-packages/chardet/chardistribution.py b/venv/lib/python3.7/site-packages/chardet/chardistribution.py new file mode 100644 index 0000000..c0395f4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/chardistribution.py @@ -0,0 +1,233 @@ +######################## 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 ######################### + +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): + ENOUGH_DATA_THRESHOLD = 1024 + SURE_YES = 0.99 + SURE_NO = 0.01 + MINIMUM_DATA_THRESHOLD = 3 + + def __init__(self): + # 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 + # 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.reset() + + def reset(self): + """reset analyser, clear any state""" + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + self._total_chars = 0 # Total characters encountered + # The number of characters whose frequency order is less than 512 + self._freq_chars = 0 + + def feed(self, char, char_len): + """feed a character with known length""" + if char_len == 2: + # we only care about 2-bytes character in our distribution analysis + order = self.get_order(char) + else: + order = -1 + if order >= 0: + self._total_chars += 1 + # order is valid + if order < self._table_size: + if 512 > self._char_to_freq_order[order]: + self._freq_chars += 1 + + def get_confidence(self): + """return confidence based on existing data""" + # if we didn't receive any character in our consideration range, + # return negative answer + if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: + 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)) + 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): + # 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): + # 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 + # table. + return -1 + + +class EUCTWDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCTWDistributionAnalysis, self).__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): + # for euc-TW encoding, we are interested + # first byte range: 0xc4 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xC4: + return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 + else: + return -1 + + +class EUCKRDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCKRDistributionAnalysis, self).__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): + # for euc-KR encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xB0: + return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 + else: + return -1 + + +class GB2312DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(GB2312DistributionAnalysis, self).__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): + # for GB2312 encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + 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 + + +class Big5DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(Big5DistributionAnalysis, self).__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): + # for big5 encoding, we are interested + # first byte range: 0xa4 -- 0xfe + # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + 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 + + +class SJISDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(SJISDistributionAnalysis, self).__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): + # 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): + order = 188 * (first_char - 0x81) + elif (first_char >= 0xE0) and (first_char <= 0xEF): + order = 188 * (first_char - 0xE0 + 31) + else: + return -1 + order = order + second_char - 0x40 + if second_char > 0x7F: + order = -1 + return order + + +class EUCJPDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCJPDistributionAnalysis, self).__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): + # 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 diff --git a/venv/lib/python3.7/site-packages/chardet/charsetgroupprober.py b/venv/lib/python3.7/site-packages/chardet/charsetgroupprober.py new file mode 100644 index 0000000..8b3738e --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/charsetgroupprober.py @@ -0,0 +1,106 @@ +######################## 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 ######################### + +from .enums import ProbingState +from .charsetprober import CharSetProber + + +class CharSetGroupProber(CharSetProber): + def __init__(self, lang_filter=None): + super(CharSetGroupProber, self).__init__(lang_filter=lang_filter) + self._active_num = 0 + self.probers = [] + self._best_guess_prober = None + + def reset(self): + super(CharSetGroupProber, self).reset() + self._active_num = 0 + for prober in self.probers: + if prober: + prober.reset() + prober.active = True + self._active_num += 1 + self._best_guess_prober = None + + @property + def charset_name(self): + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.charset_name + + @property + def language(self): + 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): + for prober in self.probers: + if not prober: + continue + if not prober.active: + continue + state = prober.feed(byte_str) + if not state: + continue + if state == ProbingState.FOUND_IT: + self._best_guess_prober = prober + return self.state + elif state == ProbingState.NOT_ME: + prober.active = False + self._active_num -= 1 + if self._active_num <= 0: + self._state = ProbingState.NOT_ME + return self.state + return self.state + + def get_confidence(self): + state = self.state + if state == ProbingState.FOUND_IT: + return 0.99 + elif 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) + continue + conf = prober.get_confidence() + 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 + if not self._best_guess_prober: + return 0.0 + return best_conf diff --git a/venv/lib/python3.7/site-packages/chardet/charsetprober.py b/venv/lib/python3.7/site-packages/chardet/charsetprober.py new file mode 100644 index 0000000..eac4e59 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/charsetprober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 ######################### + +import logging +import re + +from .enums import ProbingState + + +class CharSetProber(object): + + SHORTCUT_THRESHOLD = 0.95 + + def __init__(self, lang_filter=None): + self._state = None + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + + def reset(self): + self._state = ProbingState.DETECTING + + @property + def charset_name(self): + return None + + def feed(self, buf): + pass + + @property + def state(self): + return self._state + + def get_confidence(self): + return 0.0 + + @staticmethod + def filter_high_byte_only(buf): + buf = re.sub(b'([\x00-\x7F])+', b' ', buf) + return buf + + @staticmethod + def filter_international_words(buf): + """ + 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() + + # 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) + + for word in words: + filtered.extend(word[:-1]) + + # If the last character in the word is a marker, replace it with a + # space as markers shouldn't affect our analysis (they are used + # 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' ' + filtered.extend(last_char) + + return filtered + + @staticmethod + def filter_with_english_letters(buf): + """ + 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``. + """ + filtered = bytearray() + in_tag = False + prev = 0 + + 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 + + # 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 + 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 + + # If we're not in a tag... + if not in_tag: + # Keep everything after last non-extended-ASCII, non-alphabetic + # character + filtered.extend(buf[prev:]) + + return filtered diff --git a/venv/lib/python3.7/site-packages/chardet/cli/__init__.py b/venv/lib/python3.7/site-packages/chardet/cli/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/cli/__init__.py @@ -0,0 +1 @@ + diff --git a/venv/lib/python3.7/site-packages/chardet/cli/chardetect.py b/venv/lib/python3.7/site-packages/chardet/cli/chardetect.py new file mode 100644 index 0000000..f0a4cc5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/cli/chardetect.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +""" +Script which takes one or more file paths and reports on their detected +encodings + +Example:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +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 chardet import __version__ +from chardet.compat import PY2 +from chardet.universaldetector import UniversalDetector + + +def description_of(lines, name='stdin'): + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + """ + u = UniversalDetector() + for line in lines: + line = bytearray(line) + u.feed(line) + # shortcut out of the loop to save reading further - particularly useful if we read a BOM. + if u.done: + break + u.close() + result = u.result + if PY2: + name = name.decode(sys.getfilesystemencoding(), 'ignore') + if result['encoding']: + return '{0}: {1} with confidence {2}'.format(name, result['encoding'], + result['confidence']) + else: + return '{0}: no result'.format(name) + + +def main(argv=None): + """ + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + """ + # 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 {0}'.format(__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__': + main() diff --git a/venv/lib/python3.7/site-packages/chardet/codingstatemachine.py b/venv/lib/python3.7/site-packages/chardet/codingstatemachine.py new file mode 100644 index 0000000..68fba44 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/codingstatemachine.py @@ -0,0 +1,88 @@ +######################## 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): +# 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 ######################### + +import logging + +from .enums import MachineState + + +class CodingStateMachine(object): + """ + 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 + state machine available, one byte at a time. The state machine changes its + state based on its previous state and the byte it receives. There are 3 + states in a state machine that are of interest to an auto-detector: + + START state: This is the state to start with, or a legal byte sequence + (i.e. a valid code point) for character has been identified. + + ME state: This indicates that the state machine identified a byte sequence + that is specific to the charset it is designed for and that + there is no other possible encoding which can contain this byte + sequence. This will to lead to an immediate positive answer for + the detector. + + ERROR state: This indicates the state machine identified an illegal byte + sequence for that encoding. This will lead to an immediate + negative answer for this encoding. Detector will exclude this + encoding from consideration from here on. + """ + def __init__(self, sm): + self._model = sm + self._curr_byte_pos = 0 + self._curr_char_len = 0 + self._curr_state = None + self.logger = logging.getLogger(__name__) + self.reset() + + def reset(self): + self._curr_state = MachineState.START + + def next_state(self, c): + # for each byte we get its class + # if it is first byte, we also get byte length + 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] + # 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] + self._curr_byte_pos += 1 + return self._curr_state + + def get_current_charlen(self): + return self._curr_char_len + + def get_coding_state_machine(self): + return self._model['name'] + + @property + def language(self): + return self._model['language'] diff --git a/venv/lib/python3.7/site-packages/chardet/compat.py b/venv/lib/python3.7/site-packages/chardet/compat.py new file mode 100644 index 0000000..ddd7468 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/compat.py @@ -0,0 +1,34 @@ +######################## BEGIN LICENSE BLOCK ######################## +# Contributor(s): +# Dan Blanchard +# Ian Cordasco +# +# 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 ######################### + +import sys + + +if sys.version_info < (3, 0): + PY2 = True + PY3 = False + base_str = (str, unicode) + text_type = unicode +else: + PY2 = False + PY3 = True + base_str = (bytes, str) + text_type = str diff --git a/venv/lib/python3.7/site-packages/chardet/cp949prober.py b/venv/lib/python3.7/site-packages/chardet/cp949prober.py new file mode 100644 index 0000000..efd793a --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/cp949prober.py @@ -0,0 +1,49 @@ +######################## 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): +# 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 ######################### + +from .chardistribution import EUCKRDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import CP949_SM_MODEL + + +class CP949Prober(MultiByteCharSetProber): + def __init__(self): + super(CP949Prober, self).__init__() + self.coding_sm = CodingStateMachine(CP949_SM_MODEL) + # NOTE: CP949 is a superset of EUC-KR, so the distribution should be + # not different. + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "CP949" + + @property + def language(self): + return "Korean" diff --git a/venv/lib/python3.7/site-packages/chardet/enums.py b/venv/lib/python3.7/site-packages/chardet/enums.py new file mode 100644 index 0000000..0451207 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/enums.py @@ -0,0 +1,76 @@ +""" +All of the Enums that are used throughout the chardet package. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + + +class InputState(object): + """ + This enum represents the different states a universal detector can be in. + """ + PURE_ASCII = 0 + ESC_ASCII = 1 + HIGH_BYTE = 2 + + +class LanguageFilter(object): + """ + This enum represents the different language filters we can apply to a + ``UniversalDetector``. + """ + CHINESE_SIMPLIFIED = 0x01 + CHINESE_TRADITIONAL = 0x02 + JAPANESE = 0x04 + KOREAN = 0x08 + NON_CJK = 0x10 + ALL = 0x1F + CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL + CJK = CHINESE | JAPANESE | KOREAN + + +class ProbingState(object): + """ + This enum represents the different states a prober can be in. + """ + DETECTING = 0 + FOUND_IT = 1 + NOT_ME = 2 + + +class MachineState(object): + """ + This enum represents the different states a state machine can be in. + """ + START = 0 + ERROR = 1 + ITS_ME = 2 + + +class SequenceLikelihood(object): + """ + 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): + """:returns: The number of likelihood categories in the enum.""" + return 4 + + +class CharacterCategory(object): + """ + 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 + DIGIT = 252 + CONTROL = 251 diff --git a/venv/lib/python3.7/site-packages/chardet/escprober.py b/venv/lib/python3.7/site-packages/chardet/escprober.py new file mode 100644 index 0000000..c70493f --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/escprober.py @@ -0,0 +1,101 @@ +######################## 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): +# 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 ######################### + +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) + + +class EscCharSetProber(CharSetProber): + """ + This CharSetProber uses a "code scheme" approach for detecting encodings, + whereby easily recognizable escape or shift sequences are relied on to + identify these encodings. + """ + + def __init__(self, lang_filter=None): + super(EscCharSetProber, self).__init__(lang_filter=lang_filter) + self.coding_sm = [] + if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: + self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) + self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) + if self.lang_filter & LanguageFilter.JAPANESE: + 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.reset() + + def reset(self): + super(EscCharSetProber, self).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) + self._detected_charset = None + self._detected_language = None + + @property + def charset_name(self): + return self._detected_charset + + @property + def language(self): + return self._detected_language + + def get_confidence(self): + if self._detected_charset: + return 0.99 + else: + return 0.00 + + def feed(self, byte_str): + for c in byte_str: + for coding_sm in self.coding_sm: + if not coding_sm or not coding_sm.active: + continue + coding_state = coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + coding_sm.active = False + self.active_sm_count -= 1 + if self.active_sm_count <= 0: + self._state = ProbingState.NOT_ME + return self.state + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + self._detected_charset = coding_sm.get_coding_state_machine() + self._detected_language = coding_sm.language + return self.state + + return self.state diff --git a/venv/lib/python3.7/site-packages/chardet/escsm.py b/venv/lib/python3.7/site-packages/chardet/escsm.py new file mode 100644 index 0000000..0069523 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/escsm.py @@ -0,0 +1,246 @@ +######################## 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): +# 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 ######################### + +from .enums import MachineState + +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 +) + +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 +) + +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'} + +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 +) + +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 +) + +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'} + +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 +) + +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 +) + +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'} + +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 +) + +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 +) + +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'} + + diff --git a/venv/lib/python3.7/site-packages/chardet/eucjpprober.py b/venv/lib/python3.7/site-packages/chardet/eucjpprober.py new file mode 100644 index 0000000..20ce8f7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/eucjpprober.py @@ -0,0 +1,92 @@ +######################## 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): +# 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 ######################### + +from .enums import ProbingState, MachineState +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCJPDistributionAnalysis +from .jpcntx import EUCJPContextAnalysis +from .mbcssm import EUCJP_SM_MODEL + + +class EUCJPProber(MultiByteCharSetProber): + def __init__(self): + super(EUCJPProber, self).__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() + self.context_analyzer.reset() + + @property + def charset_name(self): + return "EUC-JP" + + @property + def language(self): + 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]) + if coding_state == MachineState.ERROR: + 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: + self._state = ProbingState.FOUND_IT + break + elif 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, 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._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)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/venv/lib/python3.7/site-packages/chardet/euckrfreq.py b/venv/lib/python3.7/site-packages/chardet/euckrfreq.py new file mode 100644 index 0000000..b68078c --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/euckrfreq.py @@ -0,0 +1,195 @@ +######################## 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 ######################### + +# Sampling from about 20M text materials include literature and computer technology + +# 128 --> 0.79 +# 256 --> 0.92 +# 512 --> 0.986 +# 1024 --> 0.99944 +# 2048 --> 0.99999 +# +# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 +# Random Distribution Ration = 512 / (2350-512) = 0.279. +# +# Typical Distribution Ratio + +EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 + +EUCKR_TABLE_SIZE = 2352 + +# Char to FreqOrder table , +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, +1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, + 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, + 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, + 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, +1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, + 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, + 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, +1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, +1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, +1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, +1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, +1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, + 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, +1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, +1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, +1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, +1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, + 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, +1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, + 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, + 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, +1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, + 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, +1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, + 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, + 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, +1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, +1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, +1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, +1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, + 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, +1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, + 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, + 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, +1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, +1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, +1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, +1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, +1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, +1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, + 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, + 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, + 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, +1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, + 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, +1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, + 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, + 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, +2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, + 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, + 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, +2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, +2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, +2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, + 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, + 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, +2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, + 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, +1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, +2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, +1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, +2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, +2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, +1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, + 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, +2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, +2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, + 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, + 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, +2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, +1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, +2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, +2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, +2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, +2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, +2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, +2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, +1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, +2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, +2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, +2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, +2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, +2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, +1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, +1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, +2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, +1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, +2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, +1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, + 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, +2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, + 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, +2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, + 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, +2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, +2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, + 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, +2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, +1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, + 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, +1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, +2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, +1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, +2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, + 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, +2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, +1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, +2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, +1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, +2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, +1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, + 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, +2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, +2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, + 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, + 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, +1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, +1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, + 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, +2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, +2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, + 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, + 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, + 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, +2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, + 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, + 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, +2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, +2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, + 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, +2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, +1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, + 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, +2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, +2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, +2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, + 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, + 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, + 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, +2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, +2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, +2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, +1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, +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 +) + diff --git a/venv/lib/python3.7/site-packages/chardet/euckrprober.py b/venv/lib/python3.7/site-packages/chardet/euckrprober.py new file mode 100644 index 0000000..345a060 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/euckrprober.py @@ -0,0 +1,47 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCKRDistributionAnalysis +from .mbcssm import EUCKR_SM_MODEL + + +class EUCKRProber(MultiByteCharSetProber): + def __init__(self): + super(EUCKRProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-KR" + + @property + def language(self): + return "Korean" diff --git a/venv/lib/python3.7/site-packages/chardet/euctwfreq.py b/venv/lib/python3.7/site-packages/chardet/euctwfreq.py new file mode 100644 index 0000000..ed7a995 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/euctwfreq.py @@ -0,0 +1,387 @@ +######################## 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 ######################### + +# EUCTW frequency table +# Converted from big5 work +# by Taiwan's Mandarin Promotion Council +# + +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table , +EUCTW_TABLE_SIZE = 5376 + +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 +) + diff --git a/venv/lib/python3.7/site-packages/chardet/euctwprober.py b/venv/lib/python3.7/site-packages/chardet/euctwprober.py new file mode 100644 index 0000000..35669cc --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/euctwprober.py @@ -0,0 +1,46 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCTWDistributionAnalysis +from .mbcssm import EUCTW_SM_MODEL + +class EUCTWProber(MultiByteCharSetProber): + def __init__(self): + super(EUCTWProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) + self.distribution_analyzer = EUCTWDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-TW" + + @property + def language(self): + return "Taiwan" diff --git a/venv/lib/python3.7/site-packages/chardet/gb2312freq.py b/venv/lib/python3.7/site-packages/chardet/gb2312freq.py new file mode 100644 index 0000000..697837b --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/gb2312freq.py @@ -0,0 +1,283 @@ +######################## 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 ######################### + +# GB2312 most frequently used character table +# +# Char to FreqOrder table , from hz6763 + +# 512 --> 0.79 -- 0.79 +# 1024 --> 0.92 -- 0.13 +# 2048 --> 0.98 -- 0.06 +# 6768 --> 1.00 -- 0.02 +# +# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 +# Random Distribution Ration = 512 / (3755 - 512) = 0.157 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR + +GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 + +GB2312_TABLE_SIZE = 3760 + +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, +2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, + 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, +1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, +1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, + 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, +1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, +2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, +3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, + 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, +1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, + 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, +2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, + 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, +2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, +1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, +3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, + 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, +1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, + 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, +2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, +1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, +3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, +1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, +2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, +1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, + 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, +3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, +3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, + 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, +3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, + 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, +1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, +3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, +2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, +1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, + 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, +1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, +4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, + 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, +3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, +3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, + 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, +1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, +2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, +1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, +1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, + 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, +3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, +3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, +4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, + 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, +3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, +1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, +1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, +4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, + 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, + 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, +3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, +1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, + 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, +1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, +2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, + 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, + 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, + 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, +3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, +4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, +3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, + 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, +2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, +2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, +2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, + 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, +2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, + 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, + 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, + 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, +3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, +2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, +2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, +1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, + 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, +2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, + 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, + 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, +1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, +1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, + 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, + 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, +1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, +2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, +3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, +2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, +2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, +2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, +3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, +1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, +1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, +2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, +1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, +3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, +1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, +1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, +3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, + 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, +2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, +1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, +4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, +1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, +1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, +3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, +1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, + 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, + 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, +1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, + 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, +1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, +1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, + 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, +3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, +4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, +3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, +2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, +2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, +1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, +3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, +2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, +1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, +1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, + 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, +2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, +2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, +3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, +4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, +3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, + 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, +3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, +2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, +1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, + 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, + 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, +3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, +4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, +2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, +1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, +1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, + 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, +1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, +3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, + 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, + 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, +1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, + 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, +1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, + 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, +2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, + 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, +2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, +2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, +1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, +1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, +2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, + 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, +1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, +1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, +2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, +2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, +3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, +1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, +4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, + 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, + 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, +3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, +1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, + 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, +3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, +1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, +4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, +1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, +2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, +1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, + 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, +1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, +3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, + 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, +2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, + 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, +1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, +1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, +1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, +3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, +2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, +3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, +3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, +3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, + 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, +2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, + 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, +2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, + 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, +1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, + 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, + 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, +1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, +3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, +3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, +1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, +1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, +3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, +2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, +2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, +1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, +3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, + 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, +4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, +1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, +2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, +3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, +3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, +1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, + 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, + 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, +2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, + 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, +1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, + 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, +1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, +1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, +1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, +1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, +1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, + 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 +) + diff --git a/venv/lib/python3.7/site-packages/chardet/gb2312prober.py b/venv/lib/python3.7/site-packages/chardet/gb2312prober.py new file mode 100644 index 0000000..8446d2d --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/gb2312prober.py @@ -0,0 +1,46 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import GB2312DistributionAnalysis +from .mbcssm import GB2312_SM_MODEL + +class GB2312Prober(MultiByteCharSetProber): + def __init__(self): + super(GB2312Prober, self).__init__() + self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) + self.distribution_analyzer = GB2312DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "GB2312" + + @property + def language(self): + return "Chinese" diff --git a/venv/lib/python3.7/site-packages/chardet/hebrewprober.py b/venv/lib/python3.7/site-packages/chardet/hebrewprober.py new file mode 100644 index 0000000..b0e1bf4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/hebrewprober.py @@ -0,0 +1,292 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Shy Shalom +# Portions created by the Initial Developer are Copyright (C) 2005 +# 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 ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState + +# This prober doesn't actually recognize a language or a charset. +# It is a helper prober for the use of the Hebrew model probers + +### General ideas of the Hebrew charset recognition ### +# +# Four main charsets exist in Hebrew: +# "ISO-8859-8" - Visual Hebrew +# "windows-1255" - Logical Hebrew +# "ISO-8859-8-I" - Logical Hebrew +# "x-mac-hebrew" - ?? Logical Hebrew ?? +# +# Both "ISO" charsets use a completely identical set of code points, whereas +# "windows-1255" and "x-mac-hebrew" are two different proper supersets of +# these code points. windows-1255 defines additional characters in the range +# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific +# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. +# x-mac-hebrew defines similar additional code points but with a different +# mapping. +# +# As far as an average Hebrew text with no diacritics is concerned, all four +# charsets are identical with respect to code points. Meaning that for the +# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters +# (including final letters). +# +# The dominant difference between these charsets is their directionality. +# "Visual" directionality means that the text is ordered as if the renderer is +# not aware of a BIDI rendering algorithm. The renderer sees the text and +# draws it from left to right. The text itself when ordered naturally is read +# backwards. A buffer of Visual Hebrew generally looks like so: +# "[last word of first line spelled backwards] [whole line ordered backwards +# and spelled backwards] [first word of first line spelled backwards] +# [end of line] [last word of second line] ... etc' " +# adding punctuation marks, numbers and English text to visual text is +# naturally also "visual" and from left to right. +# +# "Logical" directionality means the text is ordered "naturally" according to +# the order it is read. It is the responsibility of the renderer to display +# the text from right to left. A BIDI algorithm is used to place general +# punctuation marks, numbers and English text in the text. +# +# Texts in x-mac-hebrew are almost impossible to find on the Internet. From +# what little evidence I could find, it seems that its general directionality +# is Logical. +# +# To sum up all of the above, the Hebrew probing mechanism knows about two +# charsets: +# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are +# backwards while line order is natural. For charset recognition purposes +# the line order is unimportant (In fact, for this implementation, even +# word order is unimportant). +# Logical Hebrew - "windows-1255" - normal, naturally ordered text. +# +# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be +# specifically identified. +# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew +# that contain special punctuation marks or diacritics is displayed with +# some unconverted characters showing as question marks. This problem might +# be corrected using another model prober for x-mac-hebrew. Due to the fact +# that x-mac-hebrew texts are so rare, writing another model prober isn't +# worth the effort and performance hit. +# +#### The Prober #### +# +# The prober is divided between two SBCharSetProbers and a HebrewProber, +# all of which are managed, created, fed data, inquired and deleted by the +# SBCSGroupProber. The two SBCharSetProbers identify that the text is in +# fact some kind of Hebrew, Logical or Visual. The final decision about which +# one is it is made by the HebrewProber by combining final-letter scores +# with the scores of the two SBCharSetProbers to produce a final answer. +# +# The SBCSGroupProber is responsible for stripping the original text of HTML +# tags, English characters, numbers, low-ASCII punctuation characters, spaces +# and new lines. It reduces any sequence of such characters to a single space. +# The buffer fed to each prober in the SBCS group prober is pure text in +# high-ASCII. +# The two SBCharSetProbers (model probers) share the same language model: +# Win1255Model. +# The first SBCharSetProber uses the model normally as any other +# SBCharSetProber does, to recognize windows-1255, upon which this model was +# built. The second SBCharSetProber is told to make the pair-of-letter +# lookup in the language model backwards. This in practice exactly simulates +# a visual Hebrew model using the windows-1255 logical Hebrew model. +# +# The HebrewProber is not using any language model. All it does is look for +# final-letter evidence suggesting the text is either logical Hebrew or visual +# Hebrew. Disjointed from the model probers, the results of the HebrewProber +# alone are meaningless. HebrewProber always returns 0.00 as confidence +# since it never identifies a charset by itself. Instead, the pointer to the +# HebrewProber is passed to the model probers as a helper "Name Prober". +# When the Group prober receives a positive identification from any prober, +# it asks for the name of the charset identified. If the prober queried is a +# Hebrew model prober, the model prober forwards the call to the +# HebrewProber to make the final decision. In the HebrewProber, the +# decision is made according to the final-letters scores maintained and Both +# 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): + # 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 + + # Minimum Visual vs Logical final letter score difference. + # If the difference is below this, don't rely solely on the final letter score + # distance. + MIN_FINAL_CHAR_DISTANCE = 5 + + # Minimum Visual vs Logical model score difference. + # If the difference is below this, don't rely at all on the model score + # distance. + MIN_MODEL_DISTANCE = 0.01 + + 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 + self.reset() + + def reset(self): + 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 = ' ' + # These probers are owned by the group prober. + + def set_model_probers(self, logicalProber, visualProber): + self._logical_prober = logicalProber + self._visual_prober = visualProber + + 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_non_final(self, c): + # 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 + # causing the Non-Final tsadi to appear at an end of a word even + # though this is not the case in the original text. + # The letters Pe and Kaf rarely display a related behavior of not being + # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' + # 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] + + def feed(self, byte_str): + # Final letter analysis for logical-visual decision. + # Look for evidence that the received buffer is either logical Hebrew + # or visual Hebrew. + # The following cases are checked: + # 1) A word longer than 1 letter, ending with a final letter. This is + # an indication that the text is laid out "naturally" since the + # final letter really appears at the end. +1 for logical score. + # 2) A word longer than 1 letter, ending with a Non-Final letter. In + # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, + # should not end with the Non-Final form of that letter. Exceptions + # to this rule are mentioned above in isNonFinal(). This is an + # indication that the text is laid out backwards. +1 for visual + # score + # 3) A word longer than 1 letter, starting with a final letter. Final + # letters should not appear at the beginning of a word. This is an + # indication that the text is laid out backwards. +1 for visual + # score. + # + # The visual score and logical score are accumulated throughout the + # text and are finally checked against each other in GetCharSetName(). + # No checking for final letters in the middle of words is done since + # that case is not an indication for either Logical or Visual text. + # + # We automatically filter out all 7-bit characters (replace them with + # spaces) so the word boundary detection works properly. [MAP] + + if self.state == ProbingState.NOT_ME: + # Both model probers say it's not them. No reason to continue. + return ProbingState.NOT_ME + + byte_str = self.filter_high_byte_only(byte_str) + + for cur in byte_str: + if cur == ' ': + # We stand on a space - a word just ended + if self._before_prev != ' ': + # next-to-last char was not a space so self._prev is not a + # 1 letter word + if self.is_final(self._prev): + # case (1) [-2:not space][-1:final letter][cur:space] + self._final_char_logical_score += 1 + elif self.is_non_final(self._prev): + # case (2) [-2:not space][-1:Non-Final letter][ + # cur:space] + self._final_char_visual_score += 1 + else: + # Not standing on a space + if ((self._before_prev == ' ') and + (self.is_final(self._prev)) and (cur != ' ')): + # case (3) [-2:space][-1:final letter][cur:not space] + self._final_char_visual_score += 1 + self._before_prev = self._prev + self._prev = cur + + # Forever detecting, till the end or until both model probers return + # ProbingState.NOT_ME (handled above) + return ProbingState.DETECTING + + @property + def charset_name(self): + # 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 + if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: + 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()) + if modelsub > self.MIN_MODEL_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if modelsub < -self.MIN_MODEL_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # Still no good, back to final letter distance, maybe it'll save the + # day. + if finalsub < 0.0: + return self.VISUAL_HEBREW_NAME + + # (finalsub > 0 - Logical) or (don't know what to do) default to + # Logical. + return self.LOGICAL_HEBREW_NAME + + @property + def language(self): + return 'Hebrew' + + @property + def state(self): + # 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): + return ProbingState.NOT_ME + return ProbingState.DETECTING diff --git a/venv/lib/python3.7/site-packages/chardet/jisfreq.py b/venv/lib/python3.7/site-packages/chardet/jisfreq.py new file mode 100644 index 0000000..83fc082 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/jisfreq.py @@ -0,0 +1,325 @@ +######################## 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 ######################### + +# Sampling from about 20M text materials include literature and computer technology +# +# Japanese frequency table, applied to both S-JIS and EUC-JP +# They are sorted in order. + +# 128 --> 0.77094 +# 256 --> 0.85710 +# 512 --> 0.92635 +# 1024 --> 0.97130 +# 2048 --> 0.99431 +# +# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 +# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 +# +# Typical Distribution Ratio, 25% of IDR + +JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 + +# Char to FreqOrder table , +JIS_TABLE_SIZE = 4368 + +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 +1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 +2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 +2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 +5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 +1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 +5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 +5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 +5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 +5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 +5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 +5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 +1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 +1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 +1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 +2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 +3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 +3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 + 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 + 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 +1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 + 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 +5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 + 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 + 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 + 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 + 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 + 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 +5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 +5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 +5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 +4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 +5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 +5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 +5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 +5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 +5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 +5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 +5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 +5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 +5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 +3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 +5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 +5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 +5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 +5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 +5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 +5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 +5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 +5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 +5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 +5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 +5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 +5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 +5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 +5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 +5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 +5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 +5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 +5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 +5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 +5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 +5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 +5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 +5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 +5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 +5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 +5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 +5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 +5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 +5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 +5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 +5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 +5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 +5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 +5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 +5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 +5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 +6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 +6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 +6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 +6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 +6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 +6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 +6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 +6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 +4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 + 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 + 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 +1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 +1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 + 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 +3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 +3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 + 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 +3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 +3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 + 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 +2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 + 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 +3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 +1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 + 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 +1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 + 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 +2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 +2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 +2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 +2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 +1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 +1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 +1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 +1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 +2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 +1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 +2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 +1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 +1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 +1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 +1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 +1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 +1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 + 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 + 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 +1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 +2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 +2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 +2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 +3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 +3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 + 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 +3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 +1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 + 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 +2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 +1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 + 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 +3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 +4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 +2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 +1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 +2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 +1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 + 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 + 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 +1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 +2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 +2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 +2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 +3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 +1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 +2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 + 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 + 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 + 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 +1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 +2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 + 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 +1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 +1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 + 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 +1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 +1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 +1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 + 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 +2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 + 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 +2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 +3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 +2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 +1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 +6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 +1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 +2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 +1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 + 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 + 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 +3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 +3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 +1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 +1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 +1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 +1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 + 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 + 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 +2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 + 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 +3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 +2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 + 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 +1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 +2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 + 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 +1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 + 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 +4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 +2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 +1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 + 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 +1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 +2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 + 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 +6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 +1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 +1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 +2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 +3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 + 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 +3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 +1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 + 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 +1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 + 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 +3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 + 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 +2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 + 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 +4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 +2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 +1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 +1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 +1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 + 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 +1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 +3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 +1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 +3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 + 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 + 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 + 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 +2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 +1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 + 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 +1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 + 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 +1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 + 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 + 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 + 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 +1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 +1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 +2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 +4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 + 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 +1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 + 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 +1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 +3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 +1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 +2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 +2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 +1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 +1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 +2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 + 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 +2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 +1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 +1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 +1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 +1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 +3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 +2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 +2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 + 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 +3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 +3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 +1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 +2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 +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 +) + + diff --git a/venv/lib/python3.7/site-packages/chardet/jpcntx.py b/venv/lib/python3.7/site-packages/chardet/jpcntx.py new file mode 100644 index 0000000..20044e4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/jpcntx.py @@ -0,0 +1,233 @@ +######################## 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 ######################### + + +# 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), +) + +class JapaneseContextAnalysis(object): + 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 + self.reset() + + def reset(self): + self._total_rel = 0 # total sequence received + # category counters, each integer counts sequence in its category + self._rel_sample = [0] * self.NUM_OF_CATEGORY + # if last byte in current buffer is not the last byte of a character, + # we need to know how many bytes to skip in next buffer + self._need_to_skip_char_num = 0 + self._last_char_order = -1 # The order of previous char + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + + def feed(self, byte_str, num_bytes): + if self._done: + return + + # The buffer we got is byte oriented, and a character may span in more than one + # buffers. In case the last one or two byte in last buffer is not + # complete, we record how many byte needed to complete that character + # and skip these bytes here. We can choose to record those bytes as + # well and analyse the character once it is complete, but since a + # character will not make much difference, by simply skipping + # 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]) + i += char_len + if i > num_bytes: + self._need_to_skip_char_num = i - num_bytes + self._last_char_order = -1 + else: + if (order != -1) and (self._last_char_order != -1): + self._total_rel += 1 + if self._total_rel > self.MAX_REL_THRESHOLD: + self._done = True + break + self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1 + self._last_char_order = order + + def got_enough_data(self): + return self._total_rel > self.ENOUGH_REL_THRESHOLD + + def get_confidence(self): + # 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 + + def get_order(self, byte_str): + return -1, 1 + +class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self): + super(SJISContextAnalysis, self).__init__() + self._charset_name = "SHIFT_JIS" + + @property + def charset_name(self): + return self._charset_name + + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): + char_len = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self._charset_name = "CP932" + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 202) and (0x9F <= second_char <= 0xF1): + return second_char - 0x9F, char_len + + return -1, char_len + +class EUCJPContextAnalysis(JapaneseContextAnalysis): + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): + char_len = 2 + elif first_char == 0x8F: + char_len = 3 + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): + return second_char - 0xA1, char_len + + return -1, char_len + + diff --git a/venv/lib/python3.7/site-packages/chardet/langbulgarianmodel.py b/venv/lib/python3.7/site-packages/chardet/langbulgarianmodel.py new file mode 100644 index 0000000..2aa4fb2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langbulgarianmodel.py @@ -0,0 +1,228 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +# this table is modified base on win1251BulgarianCharToOrderMap, so +# only number <64 is sure valid + +Latin5_BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 +210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 + 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 + 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 +) + +win1251BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 +221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 + 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 + 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 96.9392% +# first 1024 sequences:3.0618% +# rest sequences: 0.2992% +# negative sequences: 0.0020% +BulgarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, +3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, +0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, +0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, +0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, +0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,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, +1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, +0,0,0,0,0,0,0,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, +2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,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, +3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,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,1, +3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,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,1, +3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,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, +2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,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,1, +3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,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, +3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,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, +3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, +2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, +3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, +1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, +3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, +1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, +2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, +2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, +3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, +1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, +2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, +2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, +1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, +2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, +2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, +2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, +1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, +2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, +1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, +3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, +1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, +3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, +1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, +2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, +1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, +2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,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,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, +1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, +2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, +1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, +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,1,0,1,0, +1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, +2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, +1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, +0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, +1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, +1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, +1,0,0,1,3,1,1,0,0,0,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,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, +0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, +1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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,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,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, +1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, +1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,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,0,0,0,0,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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,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,1, +) + +Latin5BulgarianModel = { + 'char_to_order_map': Latin5_BulgarianCharToOrderMap, + 'precedence_matrix': BulgarianLangModel, + 'typical_positive_ratio': 0.969392, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-5", + 'language': 'Bulgairan', +} + +Win1251BulgarianModel = { + 'char_to_order_map': win1251BulgarianCharToOrderMap, + 'precedence_matrix': BulgarianLangModel, + 'typical_positive_ratio': 0.969392, + 'keep_english_letter': False, + 'charset_name': "windows-1251", + 'language': 'Bulgarian', +} diff --git a/venv/lib/python3.7/site-packages/chardet/langcyrillicmodel.py b/venv/lib/python3.7/site-packages/chardet/langcyrillicmodel.py new file mode 100644 index 0000000..e5f9a1f --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langcyrillicmodel.py @@ -0,0 +1,333 @@ +######################## 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 ######################### + +# KOI8-R language model +# Character Mapping Table: +KOI8R_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 +223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 +238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 + 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 + 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 + 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 + 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 +) + +win1251_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +) + +latin5_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +macCyrillic_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, +) + +IBM855_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, +206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, + 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, +220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, +230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, + 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, + 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, +250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, +) + +IBM866_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 97.6601% +# first 1024 sequences: 2.3389% +# rest sequences: 0.1237% +# negative sequences: 0.0009% +RussianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, +0,0,0,0,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, +3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, +0,0,0,0,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, +3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,1,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, +3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, +0,0,0,0,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, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, +0,0,0,0,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, +3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,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, +3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, +0,0,0,0,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, +3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, +0,0,0,0,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, +2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,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, +3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,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, +3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, +0,0,0,0,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, +3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,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, +3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,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, +3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,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,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,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, +3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, +0,0,0,0,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, +3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,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, +2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, +0,0,0,0,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, +3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,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, +3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,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, +2,1,3,3,3,2,0,0,0,2,1,0,1,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, +1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, +1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, +2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, +1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, +3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, +1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, +2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, +1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, +1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, +1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, +1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, +3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, +1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, +2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, +1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, +2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, +0,0,0,0,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, +2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, +1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, +1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, +1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, +3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, +3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, +1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, +1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, +0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, +1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, +1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, +0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, +1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, +2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, +1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, +1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, +2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, +1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, +1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, +1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, +0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,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, +1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, +0,1,1,0,0,1,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, +1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, +0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, +0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, +2,2,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,1, +1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,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,1, +1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,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,1, +1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, +0,1,1,1,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, +0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +) + +Koi8rModel = { + 'char_to_order_map': KOI8R_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "KOI8-R", + 'language': 'Russian', +} + +Win1251CyrillicModel = { + 'char_to_order_map': win1251_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "windows-1251", + 'language': 'Russian', +} + +Latin5CyrillicModel = { + 'char_to_order_map': latin5_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-5", + 'language': 'Russian', +} + +MacCyrillicModel = { + 'char_to_order_map': macCyrillic_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "MacCyrillic", + 'language': 'Russian', +} + +Ibm866Model = { + 'char_to_order_map': IBM866_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "IBM866", + 'language': 'Russian', +} + +Ibm855Model = { + 'char_to_order_map': IBM855_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "IBM855", + 'language': 'Russian', +} diff --git a/venv/lib/python3.7/site-packages/chardet/langgreekmodel.py b/venv/lib/python3.7/site-packages/chardet/langgreekmodel.py new file mode 100644 index 0000000..5332221 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langgreekmodel.py @@ -0,0 +1,225 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin7_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +win1253_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.2851% +# first 1024 sequences:1.7001% +# rest sequences: 0.0359% +# negative sequences: 0.0148% +GreekLangModel = ( +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,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,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, +3,0,0,0,2,0,0,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,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,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,2,0,0,0,0,0,0,0,0, +0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, +2,0,0,0,2,0,0,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,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, +0,0,0,0,2,0,0,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,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, +2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, +2,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,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,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,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,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,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,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,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, +0,0,1,0,0,0,0,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,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,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, +0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, +2,0,0,0,0,0,0,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,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,2,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,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,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, +3,0,0,0,0,0,0,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,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,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,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, +3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, +2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, +2,0,0,0,0,0,0,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,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,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,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,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,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,1,0,0,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,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,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,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,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,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,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,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, +0,0,0,0,0,0,0,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,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,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,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,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,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,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,0, +0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,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,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, +0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, +0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,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,0, +0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, +0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, +0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, +0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, +0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, +0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,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,0,0,0, +0,0,0,0,0,0,0,0,0,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,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, +0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, +0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, +0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,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,0,0,0,0, +0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, +0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, +0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,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,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, +0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, +0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, +0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,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,0,0,0,0,0, +0,0,0,0,0,0,1,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,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, +0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, +0,0,0,0,0,0,0,2,1,0,0,0,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,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,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,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,0,0,1,0,0,1,0,2,0,0,0, +0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,1,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,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, +0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,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,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin7GreekModel = { + 'char_to_order_map': Latin7_char_to_order_map, + 'precedence_matrix': GreekLangModel, + 'typical_positive_ratio': 0.982851, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-7", + 'language': 'Greek', +} + +Win1253GreekModel = { + 'char_to_order_map': win1253_char_to_order_map, + 'precedence_matrix': GreekLangModel, + 'typical_positive_ratio': 0.982851, + 'keep_english_letter': False, + 'charset_name': "windows-1253", + 'language': 'Greek', +} diff --git a/venv/lib/python3.7/site-packages/chardet/langhebrewmodel.py b/venv/lib/python3.7/site-packages/chardet/langhebrewmodel.py new file mode 100644 index 0000000..58f4c87 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langhebrewmodel.py @@ -0,0 +1,200 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Simon Montagu +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Shoshannah Forbes - 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Windows-1255 language model +# Character Mapping Table: +WIN1255_CHAR_TO_ORDER_MAP = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 + 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 +253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 + 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 +124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, +215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, + 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, +106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, + 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, +238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, + 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, + 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.4004% +# first 1024 sequences: 1.5981% +# rest sequences: 0.087% +# negative sequences: 0.0015% +HEBREW_LANG_MODEL = ( +0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, +3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, +1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, +1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, +1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, +1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, +0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, +0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, +0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, +0,2,0,1,2,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,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, +0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, +0,2,0,1,2,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,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, +0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, +0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, +0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, +0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, +0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, +0,2,1,1,1,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,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, +0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,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, +3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,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, +3,0,0,0,1,1,0,1,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,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, +0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,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, +2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, +0,0,0,1,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, +2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,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, +3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,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,1,0,0,0,0,0,0,0,0,0, +0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, +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,0,0, +3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,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, +1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, +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,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, +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, +2,1,2,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,2,2,2, +0,2,0,1,2,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,1,0, +3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,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, +2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, +0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,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, +2,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, +1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, +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,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, +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,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, +1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, +0,2,0,1,2,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, +2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,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, +1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,1,0,1,0,0,1,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, +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,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, +1,0,0,1,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,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, +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,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,0,1,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,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, +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,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, +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,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, +2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,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, +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,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, +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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,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,0,0,0,0, +0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,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, +1,0,0,1,0,0,1,0,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,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, +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,0,0,0, +1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, +0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,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, +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,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, +) + +Win1255HebrewModel = { + 'char_to_order_map': WIN1255_CHAR_TO_ORDER_MAP, + 'precedence_matrix': HEBREW_LANG_MODEL, + 'typical_positive_ratio': 0.984004, + 'keep_english_letter': False, + 'charset_name': "windows-1255", + 'language': 'Hebrew', +} diff --git a/venv/lib/python3.7/site-packages/chardet/langhungarianmodel.py b/venv/lib/python3.7/site-packages/chardet/langhungarianmodel.py new file mode 100644 index 0000000..bb7c095 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langhungarianmodel.py @@ -0,0 +1,225 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin2_HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, +175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, + 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, + 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, +245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +win1250HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, +177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, + 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, + 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, +245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 94.7368% +# first 1024 sequences:5.2623% +# rest sequences: 0.8894% +# negative sequences: 0.0009% +HungarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, +3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,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,2,0, +3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,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,1,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, +0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, +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,1,0, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,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,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,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, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,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, +3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,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,1,0, +3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,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, +3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,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, +3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, +1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, +1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,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, +1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,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, +3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,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,0, +3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, +3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, +2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, +2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, +2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, +2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, +1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, +1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, +3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, +1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, +1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, +2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, +2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, +2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, +3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,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, +2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, +1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, +1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, +1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, +2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, +1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, +2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, +2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, +1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, +1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, +0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, +2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, +2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, +1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, +1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, +2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, +2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, +2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,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, +1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, +1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, +0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,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,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +) + +Latin2HungarianModel = { + 'char_to_order_map': Latin2_HungarianCharToOrderMap, + 'precedence_matrix': HungarianLangModel, + 'typical_positive_ratio': 0.947368, + 'keep_english_letter': True, + 'charset_name': "ISO-8859-2", + 'language': 'Hungarian', +} + +Win1250HungarianModel = { + 'char_to_order_map': win1250HungarianCharToOrderMap, + 'precedence_matrix': HungarianLangModel, + 'typical_positive_ratio': 0.947368, + 'keep_english_letter': True, + 'charset_name': "windows-1250", + 'language': 'Hungarian', +} diff --git a/venv/lib/python3.7/site-packages/chardet/langthaimodel.py b/venv/lib/python3.7/site-packages/chardet/langthaimodel.py new file mode 100644 index 0000000..15f94c2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langthaimodel.py @@ -0,0 +1,199 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# The following result for thai was collected from a limited sample (1M). + +# Character Mapping Table: +TIS620CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 +188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 +253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 + 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 +209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, +223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, +236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, + 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, + 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, + 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, + 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, + 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 92.6386% +# first 1024 sequences:7.3177% +# rest sequences: 1.0230% +# negative sequences: 0.0436% +ThaiLangModel = ( +0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, +0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, +3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, +0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, +3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, +3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, +3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, +3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, +2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, +3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, +1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, +3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, +1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, +0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, +0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, +3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, +2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, +0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, +3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, +2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, +2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, +3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, +3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, +3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, +3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, +1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, +0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, +0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, +3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, +3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, +1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, +3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, +3,1,0,2,3,0,0,0,3,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,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,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,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, +0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, +0,0,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, +3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, +1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, +1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, +3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, +0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, +3,0,0,1,1,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, +3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, +0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, +0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, +0,1,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, +3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, +0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, +0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, +0,0,0,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,3,3,3,3,1,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, +1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, +0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, +0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, +0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, +3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, +2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, +0,0,0,2,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,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, +3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,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, +2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,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,0,0,0,0,0,0,0,0,0,0,0, +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,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,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, +1,0,0,1,0,1,1,0,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,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, +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,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,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, +2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, +1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +1,0,0,3,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,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, +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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,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,0, +2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,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, +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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, +1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,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,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,1,0,0,2,1,0,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,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, +2,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +TIS620ThaiModel = { + 'char_to_order_map': TIS620CharToOrderMap, + 'precedence_matrix': ThaiLangModel, + 'typical_positive_ratio': 0.926386, + 'keep_english_letter': False, + 'charset_name': "TIS-620", + 'language': 'Thai', +} diff --git a/venv/lib/python3.7/site-packages/chardet/langturkishmodel.py b/venv/lib/python3.7/site-packages/chardet/langturkishmodel.py new file mode 100644 index 0000000..a427a45 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/langturkishmodel.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +######################## 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 +# Özgür Baskın - Turkish Language Model +# +# 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin5_TurkishCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255, 23, 37, 47, 39, 29, 52, 36, 45, 53, 60, 16, 49, 20, 46, 42, + 48, 69, 44, 35, 31, 51, 38, 62, 65, 43, 56,255,255,255,255,255, +255, 1, 21, 28, 12, 2, 18, 27, 25, 3, 24, 10, 5, 13, 4, 15, + 26, 64, 7, 8, 9, 14, 32, 57, 58, 11, 22,255,255,255,255,255, +180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165, +164,163,162,161,160,159,101,158,157,156,155,154,153,152,151,106, +150,149,148,147,146,145,144,100,143,142,141,140,139,138,137,136, + 94, 80, 93,135,105,134,133, 63,132,131,130,129,128,127,126,125, +124,104, 73, 99, 79, 85,123, 54,122, 98, 92,121,120, 91,103,119, + 68,118,117, 97,116,115, 50, 90,114,113,112,111, 55, 41, 40, 86, + 89, 70, 59, 78, 71, 82, 88, 33, 77, 66, 84, 83,110, 75, 61, 96, + 30, 67,109, 74, 87,102, 34, 95, 81,108, 76, 72, 17, 6, 19,107, +) + +TurkishLangModel = ( +3,2,3,3,3,1,3,3,3,3,3,3,3,3,2,1,1,3,3,1,3,3,0,3,3,3,3,3,0,3,1,3, +3,2,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, +3,2,2,3,3,0,3,3,3,3,3,3,3,2,3,1,0,3,3,1,3,3,0,3,3,3,3,3,0,3,0,3, +3,1,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,0,1,0,1, +3,3,2,3,3,0,3,3,3,3,3,3,3,2,3,1,1,3,3,0,3,3,1,2,3,3,3,3,0,3,0,3, +3,1,1,0,0,0,1,0,0,0,0,1,1,0,1,2,1,0,0,0,1,0,0,0,0,2,0,0,0,0,0,1, +3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1,3,3,2,0,3,2,1,2,2,1,3,3,0,0,0,2, +2,2,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,1, +3,3,3,2,3,3,1,2,3,3,3,3,3,3,3,1,3,2,1,0,3,2,0,1,2,3,3,2,1,0,0,2, +2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0, +1,0,1,3,3,1,3,3,3,3,3,3,3,1,2,0,0,2,3,0,2,3,0,0,2,2,2,3,0,3,0,1, +2,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, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,0,3,2,0,2,3,2,3,3,1,0,0,2, +3,2,0,0,1,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,2,0,0,1, +3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,0,3,3,0,0,2,1,0,0,2,3,2,2,0,0,0,2, +2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,0,2,0,0,1, +3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,0,1,3,2,1,1,3,2,3,2,1,0,0,2, +2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, +3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,2,0,2,3,0,0,2,2,2,2,0,0,0,2, +3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, +3,3,3,3,3,3,3,2,2,2,2,3,2,3,3,0,3,3,1,1,2,2,0,0,2,2,3,2,0,0,1,3, +0,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1, +3,3,3,2,3,3,3,2,1,2,2,3,2,3,3,0,3,2,0,0,1,1,0,1,1,2,1,2,0,0,0,1, +0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0, +3,3,3,2,3,3,2,3,2,2,2,3,3,3,3,1,3,1,1,0,3,2,1,1,3,3,2,3,1,0,0,1, +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,1,0,2,0,0,1, +3,2,2,3,3,0,3,3,3,3,3,3,3,2,2,1,0,3,3,1,3,3,0,1,3,3,2,3,0,3,0,3, +2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +2,2,2,3,3,0,3,3,3,3,3,3,3,3,3,0,0,3,2,0,3,3,0,3,2,3,3,3,0,3,1,3, +2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, +3,3,3,1,2,3,3,1,0,0,1,0,0,3,3,2,3,0,0,2,0,0,2,0,2,0,0,0,2,0,2,0, +0,3,1,0,1,0,0,0,2,2,1,0,1,1,2,1,2,2,2,0,2,1,1,0,0,0,2,0,0,0,0,0, +1,2,1,3,3,0,3,3,3,3,3,2,3,0,0,0,0,2,3,0,2,3,1,0,2,3,1,3,0,3,0,2, +3,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, +3,3,3,1,3,3,2,2,3,2,2,0,1,2,3,0,1,2,1,0,1,0,0,0,1,0,2,2,0,0,0,1, +1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0, +3,3,3,1,3,3,1,1,3,3,1,1,3,3,1,0,2,1,2,0,2,1,0,0,1,1,2,1,0,0,0,2, +2,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, +3,3,3,1,0,2,1,3,0,0,2,0,0,3,3,0,3,0,0,1,0,1,2,0,0,1,1,2,2,0,1,0, +0,1,2,1,1,0,1,0,1,1,1,1,1,0,1,1,1,2,2,1,2,0,1,0,0,0,0,0,0,1,0,0, +3,3,3,2,3,2,3,3,0,2,2,2,3,3,3,0,3,0,0,0,2,2,0,1,2,1,1,1,0,0,0,1, +0,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,1,0,0,0, +3,3,3,3,3,3,2,1,2,2,3,3,3,3,2,0,2,0,0,0,2,2,0,0,2,1,3,3,0,0,1,1, +1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0, +1,1,2,3,3,0,3,3,3,3,3,3,2,2,0,2,0,2,3,2,3,2,2,2,2,2,2,2,1,3,2,3, +2,0,2,1,2,2,2,2,1,1,2,2,1,2,2,1,2,0,0,2,1,1,0,2,1,0,0,1,0,0,0,1, +2,3,3,1,1,1,0,1,1,1,2,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,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, +3,3,3,2,2,2,3,2,3,2,2,1,3,3,3,0,2,1,2,0,2,1,0,0,1,1,1,1,1,0,0,1, +2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, +3,3,3,2,3,3,3,3,3,2,3,1,2,3,3,1,2,0,0,0,0,0,0,0,3,2,1,1,0,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,1,0,0,0,0,0,0, +3,3,3,2,2,3,3,2,1,1,1,1,1,3,3,0,3,1,0,0,1,1,0,0,3,1,2,1,0,0,0,0, +0,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,1,0,0,0,0,0, +3,3,3,2,2,3,2,2,2,3,2,1,1,3,3,0,3,0,0,0,0,1,0,0,3,1,1,2,0,0,0,1, +1,0,0,1,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,1, +1,1,1,3,3,0,3,3,3,3,3,2,2,2,1,2,0,2,1,2,2,1,1,0,1,2,2,2,2,2,2,2, +0,0,2,1,2,1,2,1,0,1,1,3,1,2,1,1,2,0,0,2,0,1,0,1,0,1,0,0,0,1,0,1, +3,3,3,1,3,3,3,0,1,1,0,2,2,3,1,0,3,0,0,0,1,0,0,0,1,0,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,0,0,0,0,0,0, +3,3,2,0,0,2,2,1,0,0,1,0,0,3,3,1,3,0,0,1,1,0,2,0,3,0,0,0,2,0,1,1, +0,1,2,0,1,2,2,0,2,2,2,2,1,0,2,1,1,0,2,0,2,1,2,0,0,0,0,0,0,0,0,0, +3,3,3,1,3,2,3,2,0,2,2,2,1,3,2,0,2,1,2,0,1,2,0,0,1,0,2,2,0,0,0,2, +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,1,1,0,1,0,0,0, +3,3,3,0,3,3,1,1,2,3,1,0,3,2,3,0,3,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0, +1,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,3,3,0,3,3,2,3,3,2,2,0,0,0,0,1,2,0,1,3,0,0,0,3,1,1,0,3,0,2, +2,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, +3,3,3,1,2,2,1,0,3,1,1,1,1,3,3,2,3,0,0,1,0,1,2,0,2,2,0,2,2,0,2,1, +0,2,2,1,1,1,1,0,2,1,1,0,1,1,1,1,2,1,2,1,2,0,1,0,1,0,0,0,0,0,0,0, +3,3,3,0,1,1,3,0,0,1,1,0,0,2,2,0,3,0,0,1,1,0,1,0,0,0,0,0,2,0,0,0, +0,3,1,0,1,0,1,0,2,0,0,1,0,1,0,1,1,1,2,1,1,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,0,2,0,1,1,1,0,0,3,3,0,2,0,0,1,0,0,2,1,1,0,1,0,1,0,1,0, +0,2,0,1,2,0,2,0,2,1,1,0,1,0,2,1,1,0,2,1,1,0,1,0,0,0,1,1,0,0,0,0, +3,2,3,0,1,0,0,0,0,0,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,0,2,0,0,0, +0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,2,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,2,3,0,0,1,0,1,0,2,3,2,3,0,0,1,3,0,2,1,0,0,0,0,2,0,1,0, +0,2,1,0,0,1,1,0,2,1,0,0,1,0,0,1,1,0,1,1,2,0,1,0,0,0,0,1,0,0,0,0, +3,2,2,0,0,1,1,0,0,0,0,0,0,3,1,1,1,0,0,0,0,0,1,0,0,0,0,0,2,0,1,0, +0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,3,3,0,2,3,2,2,1,2,2,1,1,2,0,1,3,2,2,2,0,0,2,2,0,0,0,1,2,1, +3,0,2,1,1,0,1,1,1,0,1,2,2,2,1,1,2,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0, +0,1,1,2,3,0,3,3,3,2,2,2,2,1,0,1,0,1,0,1,2,2,0,0,2,2,1,3,1,1,2,1, +0,0,1,1,2,0,1,1,0,0,1,2,0,2,1,1,2,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0, +3,3,2,0,0,3,1,0,0,0,0,0,0,3,2,1,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, +0,2,1,1,0,0,1,0,1,2,0,0,1,1,0,0,2,1,1,1,1,0,2,0,0,0,0,0,0,0,0,0, +3,3,2,0,0,1,0,0,0,0,1,0,0,3,3,2,2,0,0,1,0,0,2,0,1,0,0,0,2,0,1,0, +0,0,1,1,0,0,2,0,2,1,0,0,1,1,2,1,2,0,2,1,2,1,1,1,0,0,1,1,0,0,0,0, +3,3,2,0,0,2,2,0,0,0,1,1,0,2,2,1,3,1,0,1,0,1,2,0,0,0,0,0,1,0,1,0, +0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,0,0,0,1,0,0,1,0,0,2,3,1,2,0,0,1,0,0,2,0,0,0,1,0,2,0,2,0, +0,1,1,2,2,1,2,0,2,1,1,0,0,1,1,0,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,1,2,1,0,0,1,1,0,3,3,1,2,0,0,1,0,0,2,0,2,0,1,1,2,0,0,0, +0,0,1,1,1,1,2,0,1,1,0,1,1,1,1,0,0,0,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +3,3,3,0,2,2,3,2,0,0,1,0,0,2,3,1,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,0, +0,1,1,0,0,0,1,0,0,1,0,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,0,0,0,0,0,0,0,1,0,0,2,2,2,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, +0,0,2,1,1,0,1,0,2,1,1,0,0,1,1,2,1,0,2,0,2,0,1,0,0,0,2,0,0,0,0,0, +0,0,0,2,2,0,2,1,1,1,1,2,2,0,0,1,0,1,0,0,1,3,0,0,0,0,1,0,0,2,1,0, +0,0,1,0,1,0,0,0,0,0,2,1,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +2,0,0,2,3,0,2,3,1,2,2,0,2,0,0,2,0,2,1,1,1,2,1,0,0,1,2,1,1,2,1,0, +1,0,2,0,1,0,1,1,0,0,2,2,1,2,1,1,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,1,2,0,0,0,1,0,0,3,2,0,1,0,0,1,0,0,2,0,0,0,1,2,1,0,1,0, +0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,2,2,0,2,2,1,1,0,1,1,1,1,1,0,0,1,2,1,1,1,0,1,0,0,0,1,1,1,1, +0,0,2,1,0,1,1,1,0,1,1,2,1,2,1,1,2,0,1,1,2,1,0,2,0,0,0,0,0,0,0,0, +3,2,2,0,0,2,0,0,0,0,0,0,0,2,2,0,2,0,0,1,0,0,2,0,0,0,0,0,2,0,0,0, +0,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,0,2,2,0,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0, +2,0,1,0,1,0,1,1,0,0,1,2,0,1,0,1,1,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0, +2,2,2,0,1,1,0,0,0,1,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,1,2,0,1,0, +0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,1,1,1,0,0,0,0,1,2,0,0,1,0,0,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,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +1,1,2,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,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,1,0,0,0,0,0,2,0,0,0,0,0,1, +0,0,1,2,2,0,2,1,2,1,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,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,1,0,0,0, +2,2,2,0,0,0,1,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,1,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,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,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, +2,2,2,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,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,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,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,2,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin5TurkishModel = { + 'char_to_order_map': Latin5_TurkishCharToOrderMap, + 'precedence_matrix': TurkishLangModel, + 'typical_positive_ratio': 0.970290, + 'keep_english_letter': True, + 'charset_name': "ISO-8859-9", + 'language': 'Turkish', +} diff --git a/venv/lib/python3.7/site-packages/chardet/latin1prober.py b/venv/lib/python3.7/site-packages/chardet/latin1prober.py new file mode 100644 index 0000000..7d1e8c2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/latin1prober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 .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 +CLASS_NUM = 8 # total classes + +Latin1_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 + OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 + OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F + UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 + OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF + ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF + ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 + ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF + ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 + ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +Latin1ClassModel = ( +# UDF OTH ASC ASS ACV ACO ASV ASO + 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, # ASO +) + + +class Latin1Prober(CharSetProber): + def __init__(self): + super(Latin1Prober, self).__init__() + self._last_char_class = None + self._freq_counter = None + self.reset() + + def reset(self): + self._last_char_class = OTH + self._freq_counter = [0] * FREQ_CAT_NUM + CharSetProber.reset(self) + + @property + def charset_name(self): + return "ISO-8859-1" + + @property + def language(self): + return "" + + def feed(self, byte_str): + byte_str = self.filter_with_english_letters(byte_str) + for c in byte_str: + char_class = Latin1_CharToClass[c] + freq = Latin1ClassModel[(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): + 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 + # lower the confidence of latin1 so that other more accurate + # detector can take priority. + confidence = confidence * 0.73 + return confidence diff --git a/venv/lib/python3.7/site-packages/chardet/mbcharsetprober.py b/venv/lib/python3.7/site-packages/chardet/mbcharsetprober.py new file mode 100644 index 0000000..6256ecf --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/mbcharsetprober.py @@ -0,0 +1,91 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# 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 .charsetprober import CharSetProber +from .enums import ProbingState, MachineState + + +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 reset(self): + super(MultiByteCharSetProber, self).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 + + @property + def language(self): + raise NotImplementedError + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + 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: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.distribution_analyzer.feed(self._last_char, char_len) + else: + 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)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + return self.distribution_analyzer.get_confidence() diff --git a/venv/lib/python3.7/site-packages/chardet/mbcsgroupprober.py b/venv/lib/python3.7/site-packages/chardet/mbcsgroupprober.py new file mode 100644 index 0000000..530abe7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/mbcsgroupprober.py @@ -0,0 +1,54 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# 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 .charsetgroupprober import CharSetGroupProber +from .utf8prober import UTF8Prober +from .sjisprober import SJISProber +from .eucjpprober import EUCJPProber +from .gb2312prober import GB2312Prober +from .euckrprober import EUCKRProber +from .cp949prober import CP949Prober +from .big5prober import Big5Prober +from .euctwprober import EUCTWProber + + +class MBCSGroupProber(CharSetGroupProber): + def __init__(self, lang_filter=None): + super(MBCSGroupProber, self).__init__(lang_filter=lang_filter) + self.probers = [ + UTF8Prober(), + SJISProber(), + EUCJPProber(), + GB2312Prober(), + EUCKRProber(), + CP949Prober(), + Big5Prober(), + EUCTWProber() + ] + self.reset() diff --git a/venv/lib/python3.7/site-packages/chardet/mbcssm.py b/venv/lib/python3.7/site-packages/chardet/mbcssm.py new file mode 100644 index 0000000..8360d0f --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/mbcssm.py @@ -0,0 +1,572 @@ +######################## 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): +# 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 ######################### + +from .enums import MachineState + +# BIG5 + +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 +) + +BIG5_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + 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 +) + +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'} + +# CP949 + +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 +) + +CP949_ST = ( +#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR + 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.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 + 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 +) + +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'} + +# EUC-JP + +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 +) + +EUCJP_ST = ( + 3, 4, 3, 5,MachineState.START,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.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + 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 +) + +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'} + +# EUC-KR + +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, # 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 +) + +EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) + +EUCKR_SM_MODEL = {'class_table': EUCKR_CLS, + 'class_factor': 4, + 'state_table': EUCKR_ST, + 'char_len_table': EUCKR_CHAR_LEN_TABLE, + 'name': 'EUC-KR'} + +# EUC-TW + +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 +) + +EUCTW_ST = ( + MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 + MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 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 +) + +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'} + +# GB2312 + +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 +) + +GB2312_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 + 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 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 +) + +# 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 +# it is used for frequency analysis only, and we are validating +# each code range there as well. So it is safe to set it to be +# 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'} + +# Shift_JIS + +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 + #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 + + +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 +) + +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'} + +# UCS2-BE + +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 +) + +UCS2BE_ST = ( + 5, 7, 7,MachineState.ERROR, 4, 3,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, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 + 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f + 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 + 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 +) + +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'} + +# UCS2-LE + +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 +) + +UCS2LE_ST = ( + 6, 6, 7, 6, 4, 3,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, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f + 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 + 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 +) + +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'} + +# UTF-8 + +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 +) + +UTF8_ST = ( + MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 + 9, 11, 8, 7, 6, 5, 4, 3,#08-0f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f + MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f + MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f + MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f + MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af + MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf + 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 +) + +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'} diff --git a/venv/lib/python3.7/site-packages/chardet/sbcharsetprober.py b/venv/lib/python3.7/site-packages/chardet/sbcharsetprober.py new file mode 100644 index 0000000..0adb51d --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/sbcharsetprober.py @@ -0,0 +1,132 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 .charsetprober import CharSetProber +from .enums import CharacterCategory, ProbingState, SequenceLikelihood + + +class SingleByteCharSetProber(CharSetProber): + SAMPLE_SIZE = 64 + 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__() + self._model = model + # TRUE if we need to reverse every pair in the model lookup + self._reversed = 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.reset() + + def reset(self): + super(SingleByteCharSetProber, self).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 + # characters that fall in our sampling range + self._freq_char = 0 + + @property + def charset_name(self): + if self._name_prober: + return self._name_prober.charset_name + else: + return self._model['charset_name'] + + @property + def language(self): + if self._name_prober: + return self._name_prober.language + else: + return self._model.get('language') + + def feed(self, byte_str): + if not self._model['keep_english_letter']: + byte_str = self.filter_international_words(byte_str) + if not byte_str: + return self.state + char_to_order_map = self._model['char_to_order_map'] + for i, c in enumerate(byte_str): + # XXX: Order is in range 1-64, so one would think we want 0-63 here, + # but that leads to 27 more test failures than before. + order = char_to_order_map[c] + # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but + # CharacterCategory.SYMBOL is actually 253, so we use CONTROL + # to make it closer to the original intent. The only difference + # is whether or not we count digits and control characters for + # _total_char purposes. + if order < CharacterCategory.CONTROL: + self._total_char += 1 + if order < self.SAMPLE_SIZE: + self._freq_char += 1 + if self._last_order < self.SAMPLE_SIZE: + self._total_seqs += 1 + if not self._reversed: + i = (self._last_order * self.SAMPLE_SIZE) + order + model = self._model['precedence_matrix'][i] + else: # reverse the order of the letters in the lookup + i = (order * self.SAMPLE_SIZE) + self._last_order + model = self._model['precedence_matrix'][i] + self._seq_counters[model] += 1 + self._last_order = order + + charset_name = self._model['charset_name'] + if self.state == ProbingState.DETECTING: + 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._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._state = ProbingState.NOT_ME + + return self.state + + def get_confidence(self): + 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 = r * self._freq_char / self._total_char + if r >= 1.0: + r = 0.99 + return r diff --git a/venv/lib/python3.7/site-packages/chardet/sbcsgroupprober.py b/venv/lib/python3.7/site-packages/chardet/sbcsgroupprober.py new file mode 100644 index 0000000..98e95dc --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/sbcsgroupprober.py @@ -0,0 +1,73 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 .charsetgroupprober import CharSetGroupProber +from .sbcharsetprober import SingleByteCharSetProber +from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, + Latin5CyrillicModel, MacCyrillicModel, + Ibm866Model, Ibm855Model) +from .langgreekmodel import Latin7GreekModel, Win1253GreekModel +from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel +# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel +from .langthaimodel import TIS620ThaiModel +from .langhebrewmodel import Win1255HebrewModel +from .hebrewprober import HebrewProber +from .langturkishmodel import Latin5TurkishModel + + +class SBCSGroupProber(CharSetGroupProber): + def __init__(self): + super(SBCSGroupProber, self).__init__() + self.probers = [ + SingleByteCharSetProber(Win1251CyrillicModel), + SingleByteCharSetProber(Koi8rModel), + SingleByteCharSetProber(Latin5CyrillicModel), + SingleByteCharSetProber(MacCyrillicModel), + SingleByteCharSetProber(Ibm866Model), + SingleByteCharSetProber(Ibm855Model), + SingleByteCharSetProber(Latin7GreekModel), + SingleByteCharSetProber(Win1253GreekModel), + SingleByteCharSetProber(Latin5BulgarianModel), + SingleByteCharSetProber(Win1251BulgarianModel), + # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) + # after we retrain model. + # SingleByteCharSetProber(Latin2HungarianModel), + # SingleByteCharSetProber(Win1250HungarianModel), + SingleByteCharSetProber(TIS620ThaiModel), + SingleByteCharSetProber(Latin5TurkishModel), + ] + hebrew_prober = HebrewProber() + logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, + False, hebrew_prober) + visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, + hebrew_prober) + hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) + self.probers.extend([hebrew_prober, logical_hebrew_prober, + visual_hebrew_prober]) + + self.reset() diff --git a/venv/lib/python3.7/site-packages/chardet/sjisprober.py b/venv/lib/python3.7/site-packages/chardet/sjisprober.py new file mode 100644 index 0000000..9e29623 --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/sjisprober.py @@ -0,0 +1,92 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import SJISDistributionAnalysis +from .jpcntx import SJISContextAnalysis +from .mbcssm import SJIS_SM_MODEL +from .enums import ProbingState, MachineState + + +class SJISProber(MultiByteCharSetProber): + def __init__(self): + super(SJISProber, self).__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() + self.context_analyzer.reset() + + @property + def charset_name(self): + return self.context_analyzer.charset_name + + @property + def language(self): + return "Japanese" + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + 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: + self._state = ProbingState.FOUND_IT + break + elif 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.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._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)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/venv/lib/python3.7/site-packages/chardet/universaldetector.py b/venv/lib/python3.7/site-packages/chardet/universaldetector.py new file mode 100644 index 0000000..7b4e92d --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/universaldetector.py @@ -0,0 +1,286 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 ######################### +""" +Module containing the UniversalDetector detector class, which is the primary +class a user of ``chardet`` should use. + +:author: Mark Pilgrim (initial port to Python) +:author: Shy Shalom (original C code) +:author: Dan Blanchard (major refactoring for 3.0) +:author: Ian Cordasco +""" + + +import codecs +import logging +import re + +from .charsetgroupprober import CharSetGroupProber +from .enums import InputState, LanguageFilter, ProbingState +from .escprober import EscCharSetProber +from .latin1prober import Latin1Prober +from .mbcsgroupprober import MBCSGroupProber +from .sbcsgroupprober import SBCSGroupProber + + +class UniversalDetector(object): + """ + The ``UniversalDetector`` class underlies the ``chardet.detect`` function + and coordinates all of the different charset probers. + + To get a ``dict`` containing an encoding and its confidence, you can simply + run: + + .. code:: + + u = UniversalDetector() + u.feed(some_bytes) + u.close() + detected = u.result + + """ + + 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 + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + self._has_win_bytes = None + self.reset() + + def reset(self): + """ + 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.done = False + self._got_data = False + self._has_win_bytes = False + self._input_state = InputState.PURE_ASCII + self._last_char = b'' + if self._esc_charset_prober: + self._esc_charset_prober.reset() + for prober in self._charset_probers: + prober.reset() + + def feed(self, byte_str): + """ + Takes a chunk of a document and feeds it through all of the relevant + charset probers. + + After calling ``feed``, you can check the value of the ``done`` + attribute to see if you need to continue feeding the + ``UniversalDetector`` more data, or if it has made a prediction + (in the ``result`` attribute). + + .. note:: + You should always call ``close`` when you're done feeding in your + document if ``done`` is not already ``True``. + """ + if self.done: + return + + if not len(byte_str): + return + + if not isinstance(byte_str, bytearray): + byte_str = bytearray(byte_str) + + # First check for known BOMs, since these are guaranteed to be correct + if not self._got_data: + # 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)): + # 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'): + # 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'): + # 00 00 FF FE UCS-4, unusual octet order BOM (2143) + self.result = {'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._got_data = True + if self.result['encoding'] is not None: + self.done = True + return + + # If none of those matched and we've only see ASCII so far, check + # for high bytes and escape sequences + 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): + self._input_state = InputState.ESC_ASCII + + self._last_char = byte_str[-1:] + + # 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 + # use such sequences. + if self._input_state == InputState.ESC_ASCII: + 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.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 + # single-byte probers that are left. The single-byte probers + # use character bigram distributions to determine the encoding, whereas + # the multi-byte probers use a combination of character unigram and + # bigram distributions. + elif self._input_state == InputState.HIGH_BYTE: + if not self._charset_probers: + self._charset_probers = [MBCSGroupProber(self.lang_filter)] + # If we're checking non-CJK encodings, use single-byte prober + if self.lang_filter & LanguageFilter.NON_CJK: + self._charset_probers.append(SBCSGroupProber()) + self._charset_probers.append(Latin1Prober()) + 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.done = True + break + if self.WIN_BYTE_DETECTOR.search(byte_str): + self._has_win_bytes = True + + def close(self): + """ + Stop analyzing the current document and come up with a final + prediction. + + :returns: The ``result`` attribute, a ``dict`` with the keys + `encoding`, `confidence`, and `language`. + """ + # Don't bother with checks if we're already done + if self.done: + return self.result + self.done = True + + if not self._got_data: + 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': ''} + + # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD + elif self._input_state == InputState.HIGH_BYTE: + prober_confidence = None + max_prober_confidence = 0.0 + max_prober = None + for prober in self._charset_probers: + if not prober: + continue + prober_confidence = prober.get_confidence() + if prober_confidence > max_prober_confidence: + max_prober_confidence = prober_confidence + 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() + 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 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} + + # 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') + 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()) + else: + self.logger.debug('%s %s confidence = %s', + prober.charset_name, + prober.language, + prober.get_confidence()) + return self.result diff --git a/venv/lib/python3.7/site-packages/chardet/utf8prober.py b/venv/lib/python3.7/site-packages/chardet/utf8prober.py new file mode 100644 index 0000000..6c3196c --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/utf8prober.py @@ -0,0 +1,82 @@ +######################## 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): +# 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 ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState, MachineState +from .codingstatemachine import CodingStateMachine +from .mbcssm import UTF8_SM_MODEL + + + +class UTF8Prober(CharSetProber): + ONE_CHAR_PROB = 0.5 + + def __init__(self): + super(UTF8Prober, self).__init__() + self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) + self._num_mb_chars = None + self.reset() + + def reset(self): + super(UTF8Prober, self).reset() + self.coding_sm.reset() + self._num_mb_chars = 0 + + @property + def charset_name(self): + return "utf-8" + + @property + def language(self): + return "" + + def feed(self, byte_str): + 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: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + if self.coding_sm.get_current_charlen() >= 2: + self._num_mb_chars += 1 + + if self.state == ProbingState.DETECTING: + if self.get_confidence() > self.SHORTCUT_THRESHOLD: + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + unlike = 0.99 + if self._num_mb_chars < 6: + unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars + return 1.0 - unlike + else: + return unlike diff --git a/venv/lib/python3.7/site-packages/chardet/version.py b/venv/lib/python3.7/site-packages/chardet/version.py new file mode 100644 index 0000000..bb2a34a --- /dev/null +++ b/venv/lib/python3.7/site-packages/chardet/version.py @@ -0,0 +1,9 @@ +""" +This module exists only to simplify retrieving the version number of chardet +from within setup.py and from chardet subpackages. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + +__version__ = "3.0.4" +VERSION = __version__.split('.') diff --git a/venv/lib/python3.7/site-packages/easy-install.pth b/venv/lib/python3.7/site-packages/easy-install.pth new file mode 100644 index 0000000..b74fe2e --- /dev/null +++ b/venv/lib/python3.7/site-packages/easy-install.pth @@ -0,0 +1,2 @@ +./setuptools-40.8.0-py3.7.egg +./pip-19.0.3-py3.7.egg diff --git a/venv/lib/python3.7/site-packages/idna-2.8.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/idna-2.8.dist-info/LICENSE.rst b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/LICENSE.rst new file mode 100644 index 0000000..3ee64fb --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/LICENSE.rst @@ -0,0 +1,80 @@ +License +------- + +Copyright (c) 2013-2018, Kim Davies. 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. + +#. Neither the name of the copyright holder nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +#. THIS SOFTWARE IS PROVIDED BY THE 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 HOLDERS 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. + +Portions of the codec implementation and unit tests are derived from the +Python standard library, which carries the `Python Software Foundation +License `_: + + Copyright (c) 2001-2014 Python Software Foundation; All Rights Reserved + +Portions of the unit tests are derived from the Unicode standard, which +is subject to the Unicode, Inc. License Agreement: + + Copyright (c) 1991-2014 Unicode, Inc. All rights reserved. + Distributed under the Terms of Use in + . + + Permission is hereby granted, free of charge, to any person obtaining + a copy of the Unicode data files and any associated documentation + (the "Data Files") or Unicode software and any associated documentation + (the "Software") to deal in the Data Files or Software + without restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, and/or sell copies of + the Data Files or Software, and to permit persons to whom the Data Files + or Software are furnished to do so, provided that + + (a) this copyright and permission notice appear with all copies + of the Data Files or Software, + + (b) this copyright and permission notice appear in associated + documentation, and + + (c) there is clear notice in each modified Data File or in the Software + as well as in the documentation associated with the Data File(s) or + Software that the data or software has been modified. + + THE DATA FILES AND SOFTWARE ARE 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 OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS + NOTICE BE LIABLE FOR ANY CLAIM, OR 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 THE DATA FILES OR SOFTWARE. + + Except as contained in this notice, the name of a copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in these Data Files or Software without prior + written authorization of the copyright holder. diff --git a/venv/lib/python3.7/site-packages/idna-2.8.dist-info/METADATA b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/METADATA new file mode 100644 index 0000000..30fde02 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/METADATA @@ -0,0 +1,239 @@ +Metadata-Version: 2.1 +Name: idna +Version: 2.8 +Summary: Internationalized Domain Names in Applications (IDNA) +Home-page: https://github.com/kjd/idna +Author: Kim Davies +Author-email: kim@cynosure.com.au +License: BSD-like +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* + +Internationalized Domain Names in Applications (IDNA) +===================================================== + +Support for the Internationalised Domain Names in Applications +(IDNA) protocol as specified in `RFC 5891 `_. +This is the latest version of the protocol and is sometimes referred to as +“IDNA 2008”. + +This library also provides support for Unicode Technical Standard 46, +`Unicode IDNA Compatibility Processing `_. + +This acts as a suitable replacement for the “encodings.idna” module that +comes with the Python standard library, but only supports the +old, deprecated IDNA specification (`RFC 3490 `_). + +Basic functions are simply executed: + +.. code-block:: pycon + + # Python 3 + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + + # Python 2 + >>> import idna + >>> idna.encode(u'ドメイン.テスト') + 'xn--eckwd4c7c.xn--zckzah' + >>> print idna.decode('xn--eckwd4c7c.xn--zckzah') + ドメイン.テスト + +Packages +-------- + +The latest tagged release version is published in the PyPI repository: + +.. image:: https://badge.fury.io/py/idna.svg + :target: http://badge.fury.io/py/idna + + +Installation +------------ + +To install this library, you can use pip: + +.. code-block:: bash + + $ pip install idna + +Alternatively, you can install the package using the bundled setup script: + +.. code-block:: bash + + $ python setup.py install + +This library works with Python 2.7 and Python 3.4 or later. + + +Usage +----- + +For typical usage, the ``encode`` and ``decode`` functions will take a domain +name argument and perform a conversion to A-labels or U-labels respectively. + +.. code-block:: pycon + + # Python 3 + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + +You may use the codec encoding and decoding methods using the +``idna.codec`` module: + +.. code-block:: pycon + + # Python 2 + >>> import idna.codec + >>> print u'домена.испытание'.encode('idna') + xn--80ahd1agd.xn--80akhbyknj4f + >>> print 'xn--80ahd1agd.xn--80akhbyknj4f'.decode('idna') + домена.испытание + +Conversions can be applied at a per-label basis using the ``ulabel`` or ``alabel`` +functions if necessary: + +.. code-block:: pycon + + # Python 2 + >>> idna.alabel(u'测试') + 'xn--0zwm56d' + +Compatibility Mapping (UTS #46) ++++++++++++++++++++++++++++++++ + +As described in `RFC 5895 `_, the IDNA +specification no longer normalizes input from different potential ways a user +may input a domain name. This functionality, known as a “mapping”, is now +considered by the specification to be a local user-interface issue distinct +from IDNA conversion functionality. + +This library provides one such mapping, that was developed by the Unicode +Consortium. Known as `Unicode IDNA Compatibility Processing `_, +it provides for both a regular mapping for typical applications, as well as +a transitional mapping to help migrate from older IDNA 2003 applications. + +For example, “Königsgäßchen” is not a permissible label as *LATIN CAPITAL +LETTER K* is not allowed (nor are capital letters in general). UTS 46 will +convert this into lower case prior to applying the IDNA conversion. + +.. code-block:: pycon + + # Python 3 + >>> import idna + >>> idna.encode(u'Königsgäßchen') + ... + idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed + >>> idna.encode('Königsgäßchen', uts46=True) + b'xn--knigsgchen-b4a3dun' + >>> print(idna.decode('xn--knigsgchen-b4a3dun')) + königsgäßchen + +Transitional processing provides conversions to help transition from the older +2003 standard to the current standard. For example, in the original IDNA +specification, the *LATIN SMALL LETTER SHARP S* (ß) was converted into two +*LATIN SMALL LETTER S* (ss), whereas in the current IDNA specification this +conversion is not performed. + +.. code-block:: pycon + + # Python 2 + >>> idna.encode(u'Königsgäßchen', uts46=True, transitional=True) + 'xn--knigsgsschen-lcb0w' + +Implementors should use transitional processing with caution, only in rare +cases where conversion from legacy labels to current labels must be performed +(i.e. IDNA implementations that pre-date 2008). For typical applications +that just need to convert labels, transitional processing is unlikely to be +beneficial and could produce unexpected incompatible results. + +``encodings.idna`` Compatibility +++++++++++++++++++++++++++++++++ + +Function calls from the Python built-in ``encodings.idna`` module are +mapped to their IDNA 2008 equivalents using the ``idna.compat`` module. +Simply substitute the ``import`` clause in your code to refer to the +new module name. + +Exceptions +---------- + +All errors raised during the conversion following the specification should +raise an exception derived from the ``idna.IDNAError`` base class. + +More specific exceptions that may be generated as ``idna.IDNABidiError`` +when the error reflects an illegal combination of left-to-right and right-to-left +characters in a label; ``idna.InvalidCodepoint`` when a specific codepoint is +an illegal character in an IDN label (i.e. INVALID); and ``idna.InvalidCodepointContext`` +when the codepoint is illegal based on its positional context (i.e. it is CONTEXTO +or CONTEXTJ but the contextual requirements are not satisfied.) + +Building and Diagnostics +------------------------ + +The IDNA and UTS 46 functionality relies upon pre-calculated lookup tables for +performance. These tables are derived from computing against eligibility criteria +in the respective standards. These tables are computed using the command-line +script ``tools/idna-data``. + +This tool will fetch relevant tables from the Unicode Consortium and perform the +required calculations to identify eligibility. It has three main modes: + +* ``idna-data make-libdata``. Generates ``idnadata.py`` and ``uts46data.py``, + the pre-calculated lookup tables using for IDNA and UTS 46 conversions. Implementors + who wish to track this library against a different Unicode version may use this tool + to manually generate a different version of the ``idnadata.py`` and ``uts46data.py`` + files. + +* ``idna-data make-table``. Generate a table of the IDNA disposition + (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix B.1 of RFC + 5892 and the pre-computed tables published by `IANA `_. + +* ``idna-data U+0061``. Prints debugging output on the various properties + associated with an individual Unicode codepoint (in this case, U+0061), that are + used to assess the IDNA and UTS 46 status of a codepoint. This is helpful in debugging + or analysis. + +The tool accepts a number of arguments, described using ``idna-data -h``. Most notably, +the ``--version`` argument allows the specification of the version of Unicode to use +in computing the table data. For example, ``idna-data --version 9.0.0 make-libdata`` +will generate library data against Unicode 9.0.0. + +Note that this script requires Python 3, but all generated library data will work +in Python 2.7. + + +Testing +------- + +The library has a test suite based on each rule of the IDNA specification, as +well as tests that are provided as part of the Unicode Technical Standard 46, +`Unicode IDNA Compatibility Processing `_. + +The tests are run automatically on each commit at Travis CI: + +.. image:: https://travis-ci.org/kjd/idna.svg?branch=master + :target: https://travis-ci.org/kjd/idna + + diff --git a/venv/lib/python3.7/site-packages/idna-2.8.dist-info/RECORD b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/RECORD new file mode 100644 index 0000000..8d4bc61 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/RECORD @@ -0,0 +1,22 @@ +idna-2.8.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +idna-2.8.dist-info/LICENSE.rst,sha256=DUvHq9SNz7FOJCVO5AQGZzf_AWcUTiIpFKIRO4eUaD4,3947 +idna-2.8.dist-info/METADATA,sha256=X4QsM_BLMPhl4gC8SEnXjvl5-gj7hvwAl7UCyR418so,8862 +idna-2.8.dist-info/RECORD,, +idna-2.8.dist-info/WHEEL,sha256=CihQvCnsGZQBGAHLEUMf0IdA4fRduS_NBUTMgCTtvPM,110 +idna-2.8.dist-info/top_level.txt,sha256=jSag9sEDqvSPftxOQy-ABfGV_RSy7oFh4zZJpODV8k0,5 +idna/__init__.py,sha256=9Nt7xpyet3DmOrPUGooDdAwmHZZu1qUAy2EaJ93kGiQ,58 +idna/__pycache__/__init__.cpython-37.pyc,, +idna/__pycache__/codec.cpython-37.pyc,, +idna/__pycache__/compat.cpython-37.pyc,, +idna/__pycache__/core.cpython-37.pyc,, +idna/__pycache__/idnadata.cpython-37.pyc,, +idna/__pycache__/intranges.cpython-37.pyc,, +idna/__pycache__/package_data.cpython-37.pyc,, +idna/__pycache__/uts46data.cpython-37.pyc,, +idna/codec.py,sha256=lvYb7yu7PhAqFaAIAdWcwgaWI2UmgseUua-1c0AsG0A,3299 +idna/compat.py,sha256=R-h29D-6mrnJzbXxymrWUW7iZUvy-26TQwZ0ij57i4U,232 +idna/core.py,sha256=JDCZZ_PLESqIgEbU8mPyoEufWwoOiIqygA17-QZIe3s,11733 +idna/idnadata.py,sha256=HXaPFw6_YAJ0qppACPu0YLAULtRs3QovRM_CCZHGdY0,40899 +idna/intranges.py,sha256=TY1lpxZIQWEP6tNqjZkFA5hgoMWOj1OBmnUG8ihT87E,1749 +idna/package_data.py,sha256=kIzeKKXEouXLR4srqwf9Q3zv-NffKSOz5aSDOJARPB0,21 +idna/uts46data.py,sha256=oLyNZ1pBaiBlj9zFzLFRd_P7J8MkRcgDisjExZR_4MY,198292 diff --git a/venv/lib/python3.7/site-packages/idna-2.8.dist-info/WHEEL b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/WHEEL new file mode 100644 index 0000000..dea0e20 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.32.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.7/site-packages/idna-2.8.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/top_level.txt new file mode 100644 index 0000000..c40472e --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna-2.8.dist-info/top_level.txt @@ -0,0 +1 @@ +idna diff --git a/venv/lib/python3.7/site-packages/idna/__init__.py b/venv/lib/python3.7/site-packages/idna/__init__.py new file mode 100644 index 0000000..847bf93 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/__init__.py @@ -0,0 +1,2 @@ +from .package_data import __version__ +from .core import * diff --git a/venv/lib/python3.7/site-packages/idna/codec.py b/venv/lib/python3.7/site-packages/idna/codec.py new file mode 100644 index 0000000..98c65ea --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/codec.py @@ -0,0 +1,118 @@ +from .core import encode, decode, alabel, ulabel, IDNAError +import codecs +import re + +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +class Codec(codecs.Codec): + + def encode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return "", 0 + + return encode(data), len(data) + + def decode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return u"", 0 + + return decode(data), len(data) + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return ("", 0) + + labels = _unicode_dots_re.split(data) + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return (u"", 0) + + # IDNA allows decoding to operate on Unicode strings, too. + if isinstance(data, unicode): + labels = _unicode_dots_re.split(data) + else: + # Must be ASCII string + data = str(data) + unicode(data, "ascii") + labels = data.split(".") + + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = u'.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = u'.' + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result = u".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + +class StreamReader(Codec, codecs.StreamReader): + pass + +def getregentry(): + return codecs.CodecInfo( + name='idna', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) diff --git a/venv/lib/python3.7/site-packages/idna/compat.py b/venv/lib/python3.7/site-packages/idna/compat.py new file mode 100644 index 0000000..4d47f33 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/compat.py @@ -0,0 +1,12 @@ +from .core import * +from .codec import * + +def ToASCII(label): + return encode(label) + +def ToUnicode(label): + return decode(label) + +def nameprep(s): + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") + diff --git a/venv/lib/python3.7/site-packages/idna/core.py b/venv/lib/python3.7/site-packages/idna/core.py new file mode 100644 index 0000000..104624a --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/core.py @@ -0,0 +1,396 @@ +from . import idnadata +import bisect +import unicodedata +import re +import sys +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b'xn--' +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +if sys.version_info[0] == 3: + unicode = str + unichr = chr + +class IDNAError(UnicodeError): + """ Base exception for all IDNA-encoding related problems """ + pass + + +class IDNABidiError(IDNAError): + """ Exception when bidirectional requirements are not satisfied """ + pass + + +class InvalidCodepoint(IDNAError): + """ Exception when a disallowed or unallocated codepoint is used """ + pass + + +class InvalidCodepointContext(IDNAError): + """ Exception when the codepoint is not valid in the context it is used """ + pass + + +def _combining_class(cp): + v = unicodedata.combining(unichr(cp)) + if v == 0: + if not unicodedata.name(unichr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + +def _is_script(cp, script): + return intranges_contain(ord(cp), idnadata.scripts[script]) + +def _punycode(s): + return s.encode('punycode') + +def _unot(s): + return 'U+{0:04X}'.format(s) + + +def valid_label_length(label): + + if len(label) > 63: + return False + return True + + +def valid_string_length(label, trailing_dot): + + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label, check_ltr=False): + + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == '': + # String likely comes from a newer version of Unicode + raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) + if direction in ['R', 'AL', 'AN']: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ['R', 'AL']: + rtl = True + elif direction == 'L': + rtl = False + else: + raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) + + valid_ending = False + number_type = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) + # Bidi rule 3 + if direction in ['R', 'AL', 'EN', 'AN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + # Bidi rule 4 + if direction in ['AN', 'EN']: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError('Can not mix numeral types in a right-to-left label') + else: + # Bidi rule 5 + if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) + # Bidi rule 6 + if direction in ['L', 'EN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + + if not valid_ending: + raise IDNABidiError('Label ends with illegal codepoint directionality') + + return True + + +def check_initial_combiner(label): + + if unicodedata.category(label[0])[0] == 'M': + raise IDNAError('Label begins with an illegal combining character') + return True + + +def check_hyphen_ok(label): + + if label[2:4] == '--': + raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') + if label[0] == '-' or label[-1] == '-': + raise IDNAError('Label must not start or end with a hyphen') + return True + + +def check_nfc(label): + + if unicodedata.normalize('NFC', label) != label: + raise IDNAError('Label must be in Normalization Form C') + + +def valid_contextj(label, pos): + + cp_value = ord(label[pos]) + + if cp_value == 0x200c: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos-1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('L'), ord('D')]: + ok = True + break + + if not ok: + return False + + ok = False + for i in range(pos+1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('R'), ord('D')]: + ok = True + break + return ok + + if cp_value == 0x200d: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + + return False + + +def valid_contexto(label, pos, exception=False): + + cp_value = ord(label[pos]) + + if cp_value == 0x00b7: + if 0 < pos < len(label)-1: + if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label)-1 and len(label) > 1: + return _is_script(label[pos + 1], 'Greek') + return False + + elif cp_value == 0x05f3 or cp_value == 0x05f4: + if pos > 0: + return _is_script(label[pos - 1], 'Hebrew') + return False + + elif cp_value == 0x30fb: + for cp in label: + if cp == u'\u30fb': + continue + if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6f0 <= ord(cp) <= 0x06f9: + return False + return True + + elif 0x6f0 <= cp_value <= 0x6f9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + +def check_label(label): + + if isinstance(label, (bytes, bytearray)): + label = label.decode('utf-8') + if len(label) == 0: + raise IDNAError('Empty Label') + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for (pos, cp) in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + except ValueError: + raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): + if not valid_contexto(label, pos): + raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) + else: + raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + + check_bidi(label) + + +def alabel(label): + + try: + label = label.encode('ascii') + ulabel(label) + if not valid_label_length(label): + raise IDNAError('Label too long') + return label + except UnicodeEncodeError: + pass + + if not label: + raise IDNAError('No Input') + + label = unicode(label) + check_label(label) + label = _punycode(label) + label = _alabel_prefix + label + + if not valid_label_length(label): + raise IDNAError('Label too long') + + return label + + +def ulabel(label): + + if not isinstance(label, (bytes, bytearray)): + try: + label = label.encode('ascii') + except UnicodeEncodeError: + check_label(label) + return label + + label = label.lower() + if label.startswith(_alabel_prefix): + label = label[len(_alabel_prefix):] + else: + check_label(label) + return label.decode('ascii') + + label = label.decode('punycode') + check_label(label) + return label + + +def uts46_remap(domain, std3_rules=True, transitional=False): + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + output = u"" + try: + for pos, char in enumerate(domain): + code_point = ord(char) + uts46row = uts46data[code_point if code_point < 256 else + bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement = uts46row[2] if len(uts46row) == 3 else None + if (status == "V" or + (status == "D" and not transitional) or + (status == "3" and not std3_rules and replacement is None)): + output += char + elif replacement is not None and (status == "M" or + (status == "3" and not std3_rules) or + (status == "D" and transitional)): + output += replacement + elif status != "I": + raise IndexError() + return unicodedata.normalize("NFC", output) + except IndexError: + raise InvalidCodepoint( + "Codepoint {0} not allowed at position {1} in {2}".format( + _unot(code_point), pos + 1, repr(domain))) + + +def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split('.') + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if labels[-1] == '': + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(b'') + s = b'.'.join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError('Domain too long') + return s + + +def decode(s, strict=False, uts46=False, std3_rules=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(u'.') + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(u'') + return u'.'.join(result) diff --git a/venv/lib/python3.7/site-packages/idna/idnadata.py b/venv/lib/python3.7/site-packages/idna/idnadata.py new file mode 100644 index 0000000..a80c959 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/idnadata.py @@ -0,0 +1,1979 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "11.0.0" +scripts = { + 'Greek': ( + 0x37000000374, + 0x37500000378, + 0x37a0000037e, + 0x37f00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038b, + 0x38c0000038d, + 0x38e000003a2, + 0x3a3000003e2, + 0x3f000000400, + 0x1d2600001d2b, + 0x1d5d00001d62, + 0x1d6600001d6b, + 0x1dbf00001dc0, + 0x1f0000001f16, + 0x1f1800001f1e, + 0x1f2000001f46, + 0x1f4800001f4e, + 0x1f5000001f58, + 0x1f5900001f5a, + 0x1f5b00001f5c, + 0x1f5d00001f5e, + 0x1f5f00001f7e, + 0x1f8000001fb5, + 0x1fb600001fc5, + 0x1fc600001fd4, + 0x1fd600001fdc, + 0x1fdd00001ff0, + 0x1ff200001ff5, + 0x1ff600001fff, + 0x212600002127, + 0xab650000ab66, + 0x101400001018f, + 0x101a0000101a1, + 0x1d2000001d246, + ), + 'Han': ( + 0x2e8000002e9a, + 0x2e9b00002ef4, + 0x2f0000002fd6, + 0x300500003006, + 0x300700003008, + 0x30210000302a, + 0x30380000303c, + 0x340000004db6, + 0x4e0000009ff0, + 0xf9000000fa6e, + 0xfa700000fada, + 0x200000002a6d7, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x2f8000002fa1e, + ), + 'Hebrew': ( + 0x591000005c8, + 0x5d0000005eb, + 0x5ef000005f5, + 0xfb1d0000fb37, + 0xfb380000fb3d, + 0xfb3e0000fb3f, + 0xfb400000fb42, + 0xfb430000fb45, + 0xfb460000fb50, + ), + 'Hiragana': ( + 0x304100003097, + 0x309d000030a0, + 0x1b0010001b11f, + 0x1f2000001f201, + ), + 'Katakana': ( + 0x30a1000030fb, + 0x30fd00003100, + 0x31f000003200, + 0x32d0000032ff, + 0x330000003358, + 0xff660000ff70, + 0xff710000ff9e, + 0x1b0000001b001, + ), +} +joining_types = { + 0x600: 85, + 0x601: 85, + 0x602: 85, + 0x603: 85, + 0x604: 85, + 0x605: 85, + 0x608: 85, + 0x60b: 85, + 0x620: 68, + 0x621: 85, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62a: 68, + 0x62b: 68, + 0x62c: 68, + 0x62d: 68, + 0x62e: 68, + 0x62f: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63a: 68, + 0x63b: 68, + 0x63c: 68, + 0x63d: 68, + 0x63e: 68, + 0x63f: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64a: 68, + 0x66e: 68, + 0x66f: 68, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x674: 85, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67a: 68, + 0x67b: 68, + 0x67c: 68, + 0x67d: 68, + 0x67e: 68, + 0x67f: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68a: 82, + 0x68b: 82, + 0x68c: 82, + 0x68d: 82, + 0x68e: 82, + 0x68f: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69a: 68, + 0x69b: 68, + 0x69c: 68, + 0x69d: 68, + 0x69e: 68, + 0x69f: 68, + 0x6a0: 68, + 0x6a1: 68, + 0x6a2: 68, + 0x6a3: 68, + 0x6a4: 68, + 0x6a5: 68, + 0x6a6: 68, + 0x6a7: 68, + 0x6a8: 68, + 0x6a9: 68, + 0x6aa: 68, + 0x6ab: 68, + 0x6ac: 68, + 0x6ad: 68, + 0x6ae: 68, + 0x6af: 68, + 0x6b0: 68, + 0x6b1: 68, + 0x6b2: 68, + 0x6b3: 68, + 0x6b4: 68, + 0x6b5: 68, + 0x6b6: 68, + 0x6b7: 68, + 0x6b8: 68, + 0x6b9: 68, + 0x6ba: 68, + 0x6bb: 68, + 0x6bc: 68, + 0x6bd: 68, + 0x6be: 68, + 0x6bf: 68, + 0x6c0: 82, + 0x6c1: 68, + 0x6c2: 68, + 0x6c3: 82, + 0x6c4: 82, + 0x6c5: 82, + 0x6c6: 82, + 0x6c7: 82, + 0x6c8: 82, + 0x6c9: 82, + 0x6ca: 82, + 0x6cb: 82, + 0x6cc: 68, + 0x6cd: 82, + 0x6ce: 68, + 0x6cf: 82, + 0x6d0: 68, + 0x6d1: 68, + 0x6d2: 82, + 0x6d3: 82, + 0x6d5: 82, + 0x6dd: 85, + 0x6ee: 82, + 0x6ef: 82, + 0x6fa: 68, + 0x6fb: 68, + 0x6fc: 68, + 0x6ff: 68, + 0x70f: 84, + 0x710: 82, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71a: 68, + 0x71b: 68, + 0x71c: 68, + 0x71d: 68, + 0x71e: 82, + 0x71f: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72a: 82, + 0x72b: 68, + 0x72c: 82, + 0x72d: 68, + 0x72e: 68, + 0x72f: 82, + 0x74d: 82, + 0x74e: 68, + 0x74f: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75a: 82, + 0x75b: 82, + 0x75c: 68, + 0x75d: 68, + 0x75e: 68, + 0x75f: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76a: 68, + 0x76b: 82, + 0x76c: 82, + 0x76d: 68, + 0x76e: 68, + 0x76f: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77a: 68, + 0x77b: 68, + 0x77c: 68, + 0x77d: 68, + 0x77e: 68, + 0x77f: 68, + 0x7ca: 68, + 0x7cb: 68, + 0x7cc: 68, + 0x7cd: 68, + 0x7ce: 68, + 0x7cf: 68, + 0x7d0: 68, + 0x7d1: 68, + 0x7d2: 68, + 0x7d3: 68, + 0x7d4: 68, + 0x7d5: 68, + 0x7d6: 68, + 0x7d7: 68, + 0x7d8: 68, + 0x7d9: 68, + 0x7da: 68, + 0x7db: 68, + 0x7dc: 68, + 0x7dd: 68, + 0x7de: 68, + 0x7df: 68, + 0x7e0: 68, + 0x7e1: 68, + 0x7e2: 68, + 0x7e3: 68, + 0x7e4: 68, + 0x7e5: 68, + 0x7e6: 68, + 0x7e7: 68, + 0x7e8: 68, + 0x7e9: 68, + 0x7ea: 68, + 0x7fa: 67, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84a: 68, + 0x84b: 68, + 0x84c: 68, + 0x84d: 68, + 0x84e: 68, + 0x84f: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 85, + 0x857: 85, + 0x858: 85, + 0x860: 68, + 0x861: 85, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x866: 85, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86a: 82, + 0x8a0: 68, + 0x8a1: 68, + 0x8a2: 68, + 0x8a3: 68, + 0x8a4: 68, + 0x8a5: 68, + 0x8a6: 68, + 0x8a7: 68, + 0x8a8: 68, + 0x8a9: 68, + 0x8aa: 82, + 0x8ab: 82, + 0x8ac: 82, + 0x8ad: 85, + 0x8ae: 82, + 0x8af: 68, + 0x8b0: 68, + 0x8b1: 82, + 0x8b2: 82, + 0x8b3: 68, + 0x8b4: 68, + 0x8b6: 68, + 0x8b7: 68, + 0x8b8: 68, + 0x8b9: 82, + 0x8ba: 68, + 0x8bb: 68, + 0x8bc: 68, + 0x8bd: 68, + 0x8e2: 85, + 0x1806: 85, + 0x1807: 68, + 0x180a: 67, + 0x180e: 85, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182a: 68, + 0x182b: 68, + 0x182c: 68, + 0x182d: 68, + 0x182e: 68, + 0x182f: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183a: 68, + 0x183b: 68, + 0x183c: 68, + 0x183d: 68, + 0x183e: 68, + 0x183f: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184a: 68, + 0x184b: 68, + 0x184c: 68, + 0x184d: 68, + 0x184e: 68, + 0x184f: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185a: 68, + 0x185b: 68, + 0x185c: 68, + 0x185d: 68, + 0x185e: 68, + 0x185f: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186a: 68, + 0x186b: 68, + 0x186c: 68, + 0x186d: 68, + 0x186e: 68, + 0x186f: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1880: 85, + 0x1881: 85, + 0x1882: 85, + 0x1883: 85, + 0x1884: 85, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188a: 68, + 0x188b: 68, + 0x188c: 68, + 0x188d: 68, + 0x188e: 68, + 0x188f: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189a: 68, + 0x189b: 68, + 0x189c: 68, + 0x189d: 68, + 0x189e: 68, + 0x189f: 68, + 0x18a0: 68, + 0x18a1: 68, + 0x18a2: 68, + 0x18a3: 68, + 0x18a4: 68, + 0x18a5: 68, + 0x18a6: 68, + 0x18a7: 68, + 0x18a8: 68, + 0x18aa: 68, + 0x200c: 85, + 0x200d: 67, + 0x202f: 85, + 0x2066: 85, + 0x2067: 85, + 0x2068: 85, + 0x2069: 85, + 0xa840: 68, + 0xa841: 68, + 0xa842: 68, + 0xa843: 68, + 0xa844: 68, + 0xa845: 68, + 0xa846: 68, + 0xa847: 68, + 0xa848: 68, + 0xa849: 68, + 0xa84a: 68, + 0xa84b: 68, + 0xa84c: 68, + 0xa84d: 68, + 0xa84e: 68, + 0xa84f: 68, + 0xa850: 68, + 0xa851: 68, + 0xa852: 68, + 0xa853: 68, + 0xa854: 68, + 0xa855: 68, + 0xa856: 68, + 0xa857: 68, + 0xa858: 68, + 0xa859: 68, + 0xa85a: 68, + 0xa85b: 68, + 0xa85c: 68, + 0xa85d: 68, + 0xa85e: 68, + 0xa85f: 68, + 0xa860: 68, + 0xa861: 68, + 0xa862: 68, + 0xa863: 68, + 0xa864: 68, + 0xa865: 68, + 0xa866: 68, + 0xa867: 68, + 0xa868: 68, + 0xa869: 68, + 0xa86a: 68, + 0xa86b: 68, + 0xa86c: 68, + 0xa86d: 68, + 0xa86e: 68, + 0xa86f: 68, + 0xa870: 68, + 0xa871: 68, + 0xa872: 76, + 0xa873: 85, + 0x10ac0: 68, + 0x10ac1: 68, + 0x10ac2: 68, + 0x10ac3: 68, + 0x10ac4: 68, + 0x10ac5: 82, + 0x10ac6: 85, + 0x10ac7: 82, + 0x10ac8: 85, + 0x10ac9: 82, + 0x10aca: 82, + 0x10acb: 85, + 0x10acc: 85, + 0x10acd: 76, + 0x10ace: 82, + 0x10acf: 82, + 0x10ad0: 82, + 0x10ad1: 82, + 0x10ad2: 82, + 0x10ad3: 68, + 0x10ad4: 68, + 0x10ad5: 68, + 0x10ad6: 68, + 0x10ad7: 76, + 0x10ad8: 68, + 0x10ad9: 68, + 0x10ada: 68, + 0x10adb: 68, + 0x10adc: 68, + 0x10add: 82, + 0x10ade: 68, + 0x10adf: 68, + 0x10ae0: 68, + 0x10ae1: 82, + 0x10ae2: 85, + 0x10ae3: 85, + 0x10ae4: 82, + 0x10aeb: 68, + 0x10aec: 68, + 0x10aed: 68, + 0x10aee: 68, + 0x10aef: 82, + 0x10b80: 68, + 0x10b81: 82, + 0x10b82: 68, + 0x10b83: 82, + 0x10b84: 82, + 0x10b85: 82, + 0x10b86: 68, + 0x10b87: 68, + 0x10b88: 68, + 0x10b89: 82, + 0x10b8a: 68, + 0x10b8b: 68, + 0x10b8c: 82, + 0x10b8d: 68, + 0x10b8e: 82, + 0x10b8f: 82, + 0x10b90: 68, + 0x10b91: 82, + 0x10ba9: 82, + 0x10baa: 82, + 0x10bab: 82, + 0x10bac: 82, + 0x10bad: 68, + 0x10bae: 68, + 0x10baf: 85, + 0x10d00: 76, + 0x10d01: 68, + 0x10d02: 68, + 0x10d03: 68, + 0x10d04: 68, + 0x10d05: 68, + 0x10d06: 68, + 0x10d07: 68, + 0x10d08: 68, + 0x10d09: 68, + 0x10d0a: 68, + 0x10d0b: 68, + 0x10d0c: 68, + 0x10d0d: 68, + 0x10d0e: 68, + 0x10d0f: 68, + 0x10d10: 68, + 0x10d11: 68, + 0x10d12: 68, + 0x10d13: 68, + 0x10d14: 68, + 0x10d15: 68, + 0x10d16: 68, + 0x10d17: 68, + 0x10d18: 68, + 0x10d19: 68, + 0x10d1a: 68, + 0x10d1b: 68, + 0x10d1c: 68, + 0x10d1d: 68, + 0x10d1e: 68, + 0x10d1f: 68, + 0x10d20: 68, + 0x10d21: 68, + 0x10d22: 82, + 0x10d23: 68, + 0x10f30: 68, + 0x10f31: 68, + 0x10f32: 68, + 0x10f33: 82, + 0x10f34: 68, + 0x10f35: 68, + 0x10f36: 68, + 0x10f37: 68, + 0x10f38: 68, + 0x10f39: 68, + 0x10f3a: 68, + 0x10f3b: 68, + 0x10f3c: 68, + 0x10f3d: 68, + 0x10f3e: 68, + 0x10f3f: 68, + 0x10f40: 68, + 0x10f41: 68, + 0x10f42: 68, + 0x10f43: 68, + 0x10f44: 68, + 0x10f45: 85, + 0x10f51: 68, + 0x10f52: 68, + 0x10f53: 68, + 0x10f54: 82, + 0x110bd: 85, + 0x110cd: 85, + 0x1e900: 68, + 0x1e901: 68, + 0x1e902: 68, + 0x1e903: 68, + 0x1e904: 68, + 0x1e905: 68, + 0x1e906: 68, + 0x1e907: 68, + 0x1e908: 68, + 0x1e909: 68, + 0x1e90a: 68, + 0x1e90b: 68, + 0x1e90c: 68, + 0x1e90d: 68, + 0x1e90e: 68, + 0x1e90f: 68, + 0x1e910: 68, + 0x1e911: 68, + 0x1e912: 68, + 0x1e913: 68, + 0x1e914: 68, + 0x1e915: 68, + 0x1e916: 68, + 0x1e917: 68, + 0x1e918: 68, + 0x1e919: 68, + 0x1e91a: 68, + 0x1e91b: 68, + 0x1e91c: 68, + 0x1e91d: 68, + 0x1e91e: 68, + 0x1e91f: 68, + 0x1e920: 68, + 0x1e921: 68, + 0x1e922: 68, + 0x1e923: 68, + 0x1e924: 68, + 0x1e925: 68, + 0x1e926: 68, + 0x1e927: 68, + 0x1e928: 68, + 0x1e929: 68, + 0x1e92a: 68, + 0x1e92b: 68, + 0x1e92c: 68, + 0x1e92d: 68, + 0x1e92e: 68, + 0x1e92f: 68, + 0x1e930: 68, + 0x1e931: 68, + 0x1e932: 68, + 0x1e933: 68, + 0x1e934: 68, + 0x1e935: 68, + 0x1e936: 68, + 0x1e937: 68, + 0x1e938: 68, + 0x1e939: 68, + 0x1e93a: 68, + 0x1e93b: 68, + 0x1e93c: 68, + 0x1e93d: 68, + 0x1e93e: 68, + 0x1e93f: 68, + 0x1e940: 68, + 0x1e941: 68, + 0x1e942: 68, + 0x1e943: 68, +} +codepoint_classes = { + 'PVALID': ( + 0x2d0000002e, + 0x300000003a, + 0x610000007b, + 0xdf000000f7, + 0xf800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010a, + 0x10b0000010c, + 0x10d0000010e, + 0x10f00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011a, + 0x11b0000011c, + 0x11d0000011e, + 0x11f00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012a, + 0x12b0000012c, + 0x12d0000012e, + 0x12f00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13a0000013b, + 0x13c0000013d, + 0x13e0000013f, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14b0000014c, + 0x14d0000014e, + 0x14f00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015a, + 0x15b0000015c, + 0x15d0000015e, + 0x15f00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016a, + 0x16b0000016c, + 0x16d0000016e, + 0x16f00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17a0000017b, + 0x17c0000017d, + 0x17e0000017f, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18c0000018e, + 0x19200000193, + 0x19500000196, + 0x1990000019c, + 0x19e0000019f, + 0x1a1000001a2, + 0x1a3000001a4, + 0x1a5000001a6, + 0x1a8000001a9, + 0x1aa000001ac, + 0x1ad000001ae, + 0x1b0000001b1, + 0x1b4000001b5, + 0x1b6000001b7, + 0x1b9000001bc, + 0x1bd000001c4, + 0x1ce000001cf, + 0x1d0000001d1, + 0x1d2000001d3, + 0x1d4000001d5, + 0x1d6000001d7, + 0x1d8000001d9, + 0x1da000001db, + 0x1dc000001de, + 0x1df000001e0, + 0x1e1000001e2, + 0x1e3000001e4, + 0x1e5000001e6, + 0x1e7000001e8, + 0x1e9000001ea, + 0x1eb000001ec, + 0x1ed000001ee, + 0x1ef000001f1, + 0x1f5000001f6, + 0x1f9000001fa, + 0x1fb000001fc, + 0x1fd000001fe, + 0x1ff00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020a, + 0x20b0000020c, + 0x20d0000020e, + 0x20f00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021a, + 0x21b0000021c, + 0x21d0000021e, + 0x21f00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022a, + 0x22b0000022c, + 0x22d0000022e, + 0x22f00000230, + 0x23100000232, + 0x2330000023a, + 0x23c0000023d, + 0x23f00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024a, + 0x24b0000024c, + 0x24d0000024e, + 0x24f000002b0, + 0x2b9000002c2, + 0x2c6000002d2, + 0x2ec000002ed, + 0x2ee000002ef, + 0x30000000340, + 0x34200000343, + 0x3460000034f, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37b0000037e, + 0x39000000391, + 0x3ac000003cf, + 0x3d7000003d8, + 0x3d9000003da, + 0x3db000003dc, + 0x3dd000003de, + 0x3df000003e0, + 0x3e1000003e2, + 0x3e3000003e4, + 0x3e5000003e6, + 0x3e7000003e8, + 0x3e9000003ea, + 0x3eb000003ec, + 0x3ed000003ee, + 0x3ef000003f0, + 0x3f3000003f4, + 0x3f8000003f9, + 0x3fb000003fd, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046a, + 0x46b0000046c, + 0x46d0000046e, + 0x46f00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047a, + 0x47b0000047c, + 0x47d0000047e, + 0x47f00000480, + 0x48100000482, + 0x48300000488, + 0x48b0000048c, + 0x48d0000048e, + 0x48f00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049a, + 0x49b0000049c, + 0x49d0000049e, + 0x49f000004a0, + 0x4a1000004a2, + 0x4a3000004a4, + 0x4a5000004a6, + 0x4a7000004a8, + 0x4a9000004aa, + 0x4ab000004ac, + 0x4ad000004ae, + 0x4af000004b0, + 0x4b1000004b2, + 0x4b3000004b4, + 0x4b5000004b6, + 0x4b7000004b8, + 0x4b9000004ba, + 0x4bb000004bc, + 0x4bd000004be, + 0x4bf000004c0, + 0x4c2000004c3, + 0x4c4000004c5, + 0x4c6000004c7, + 0x4c8000004c9, + 0x4ca000004cb, + 0x4cc000004cd, + 0x4ce000004d0, + 0x4d1000004d2, + 0x4d3000004d4, + 0x4d5000004d6, + 0x4d7000004d8, + 0x4d9000004da, + 0x4db000004dc, + 0x4dd000004de, + 0x4df000004e0, + 0x4e1000004e2, + 0x4e3000004e4, + 0x4e5000004e6, + 0x4e7000004e8, + 0x4e9000004ea, + 0x4eb000004ec, + 0x4ed000004ee, + 0x4ef000004f0, + 0x4f1000004f2, + 0x4f3000004f4, + 0x4f5000004f6, + 0x4f7000004f8, + 0x4f9000004fa, + 0x4fb000004fc, + 0x4fd000004fe, + 0x4ff00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050a, + 0x50b0000050c, + 0x50d0000050e, + 0x50f00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051a, + 0x51b0000051c, + 0x51d0000051e, + 0x51f00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052a, + 0x52b0000052c, + 0x52d0000052e, + 0x52f00000530, + 0x5590000055a, + 0x56000000587, + 0x58800000589, + 0x591000005be, + 0x5bf000005c0, + 0x5c1000005c3, + 0x5c4000005c6, + 0x5c7000005c8, + 0x5d0000005eb, + 0x5ef000005f3, + 0x6100000061b, + 0x62000000640, + 0x64100000660, + 0x66e00000675, + 0x679000006d4, + 0x6d5000006dd, + 0x6df000006e9, + 0x6ea000006f0, + 0x6fa00000700, + 0x7100000074b, + 0x74d000007b2, + 0x7c0000007f6, + 0x7fd000007fe, + 0x8000000082e, + 0x8400000085c, + 0x8600000086b, + 0x8a0000008b5, + 0x8b6000008be, + 0x8d3000008e2, + 0x8e300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098d, + 0x98f00000991, + 0x993000009a9, + 0x9aa000009b1, + 0x9b2000009b3, + 0x9b6000009ba, + 0x9bc000009c5, + 0x9c7000009c9, + 0x9cb000009cf, + 0x9d7000009d8, + 0x9e0000009e4, + 0x9e6000009f2, + 0x9fc000009fd, + 0x9fe000009ff, + 0xa0100000a04, + 0xa0500000a0b, + 0xa0f00000a11, + 0xa1300000a29, + 0xa2a00000a31, + 0xa3200000a33, + 0xa3500000a36, + 0xa3800000a3a, + 0xa3c00000a3d, + 0xa3e00000a43, + 0xa4700000a49, + 0xa4b00000a4e, + 0xa5100000a52, + 0xa5c00000a5d, + 0xa6600000a76, + 0xa8100000a84, + 0xa8500000a8e, + 0xa8f00000a92, + 0xa9300000aa9, + 0xaaa00000ab1, + 0xab200000ab4, + 0xab500000aba, + 0xabc00000ac6, + 0xac700000aca, + 0xacb00000ace, + 0xad000000ad1, + 0xae000000ae4, + 0xae600000af0, + 0xaf900000b00, + 0xb0100000b04, + 0xb0500000b0d, + 0xb0f00000b11, + 0xb1300000b29, + 0xb2a00000b31, + 0xb3200000b34, + 0xb3500000b3a, + 0xb3c00000b45, + 0xb4700000b49, + 0xb4b00000b4e, + 0xb5600000b58, + 0xb5f00000b64, + 0xb6600000b70, + 0xb7100000b72, + 0xb8200000b84, + 0xb8500000b8b, + 0xb8e00000b91, + 0xb9200000b96, + 0xb9900000b9b, + 0xb9c00000b9d, + 0xb9e00000ba0, + 0xba300000ba5, + 0xba800000bab, + 0xbae00000bba, + 0xbbe00000bc3, + 0xbc600000bc9, + 0xbca00000bce, + 0xbd000000bd1, + 0xbd700000bd8, + 0xbe600000bf0, + 0xc0000000c0d, + 0xc0e00000c11, + 0xc1200000c29, + 0xc2a00000c3a, + 0xc3d00000c45, + 0xc4600000c49, + 0xc4a00000c4e, + 0xc5500000c57, + 0xc5800000c5b, + 0xc6000000c64, + 0xc6600000c70, + 0xc8000000c84, + 0xc8500000c8d, + 0xc8e00000c91, + 0xc9200000ca9, + 0xcaa00000cb4, + 0xcb500000cba, + 0xcbc00000cc5, + 0xcc600000cc9, + 0xcca00000cce, + 0xcd500000cd7, + 0xcde00000cdf, + 0xce000000ce4, + 0xce600000cf0, + 0xcf100000cf3, + 0xd0000000d04, + 0xd0500000d0d, + 0xd0e00000d11, + 0xd1200000d45, + 0xd4600000d49, + 0xd4a00000d4f, + 0xd5400000d58, + 0xd5f00000d64, + 0xd6600000d70, + 0xd7a00000d80, + 0xd8200000d84, + 0xd8500000d97, + 0xd9a00000db2, + 0xdb300000dbc, + 0xdbd00000dbe, + 0xdc000000dc7, + 0xdca00000dcb, + 0xdcf00000dd5, + 0xdd600000dd7, + 0xdd800000de0, + 0xde600000df0, + 0xdf200000df4, + 0xe0100000e33, + 0xe3400000e3b, + 0xe4000000e4f, + 0xe5000000e5a, + 0xe8100000e83, + 0xe8400000e85, + 0xe8700000e89, + 0xe8a00000e8b, + 0xe8d00000e8e, + 0xe9400000e98, + 0xe9900000ea0, + 0xea100000ea4, + 0xea500000ea6, + 0xea700000ea8, + 0xeaa00000eac, + 0xead00000eb3, + 0xeb400000eba, + 0xebb00000ebe, + 0xec000000ec5, + 0xec600000ec7, + 0xec800000ece, + 0xed000000eda, + 0xede00000ee0, + 0xf0000000f01, + 0xf0b00000f0c, + 0xf1800000f1a, + 0xf2000000f2a, + 0xf3500000f36, + 0xf3700000f38, + 0xf3900000f3a, + 0xf3e00000f43, + 0xf4400000f48, + 0xf4900000f4d, + 0xf4e00000f52, + 0xf5300000f57, + 0xf5800000f5c, + 0xf5d00000f69, + 0xf6a00000f6d, + 0xf7100000f73, + 0xf7400000f75, + 0xf7a00000f81, + 0xf8200000f85, + 0xf8600000f93, + 0xf9400000f98, + 0xf9900000f9d, + 0xf9e00000fa2, + 0xfa300000fa7, + 0xfa800000fac, + 0xfad00000fb9, + 0xfba00000fbd, + 0xfc600000fc7, + 0x10000000104a, + 0x10500000109e, + 0x10d0000010fb, + 0x10fd00001100, + 0x120000001249, + 0x124a0000124e, + 0x125000001257, + 0x125800001259, + 0x125a0000125e, + 0x126000001289, + 0x128a0000128e, + 0x1290000012b1, + 0x12b2000012b6, + 0x12b8000012bf, + 0x12c0000012c1, + 0x12c2000012c6, + 0x12c8000012d7, + 0x12d800001311, + 0x131200001316, + 0x13180000135b, + 0x135d00001360, + 0x138000001390, + 0x13a0000013f6, + 0x14010000166d, + 0x166f00001680, + 0x16810000169b, + 0x16a0000016eb, + 0x16f1000016f9, + 0x17000000170d, + 0x170e00001715, + 0x172000001735, + 0x174000001754, + 0x17600000176d, + 0x176e00001771, + 0x177200001774, + 0x1780000017b4, + 0x17b6000017d4, + 0x17d7000017d8, + 0x17dc000017de, + 0x17e0000017ea, + 0x18100000181a, + 0x182000001879, + 0x1880000018ab, + 0x18b0000018f6, + 0x19000000191f, + 0x19200000192c, + 0x19300000193c, + 0x19460000196e, + 0x197000001975, + 0x1980000019ac, + 0x19b0000019ca, + 0x19d0000019da, + 0x1a0000001a1c, + 0x1a2000001a5f, + 0x1a6000001a7d, + 0x1a7f00001a8a, + 0x1a9000001a9a, + 0x1aa700001aa8, + 0x1ab000001abe, + 0x1b0000001b4c, + 0x1b5000001b5a, + 0x1b6b00001b74, + 0x1b8000001bf4, + 0x1c0000001c38, + 0x1c4000001c4a, + 0x1c4d00001c7e, + 0x1cd000001cd3, + 0x1cd400001cfa, + 0x1d0000001d2c, + 0x1d2f00001d30, + 0x1d3b00001d3c, + 0x1d4e00001d4f, + 0x1d6b00001d78, + 0x1d7900001d9b, + 0x1dc000001dfa, + 0x1dfb00001e00, + 0x1e0100001e02, + 0x1e0300001e04, + 0x1e0500001e06, + 0x1e0700001e08, + 0x1e0900001e0a, + 0x1e0b00001e0c, + 0x1e0d00001e0e, + 0x1e0f00001e10, + 0x1e1100001e12, + 0x1e1300001e14, + 0x1e1500001e16, + 0x1e1700001e18, + 0x1e1900001e1a, + 0x1e1b00001e1c, + 0x1e1d00001e1e, + 0x1e1f00001e20, + 0x1e2100001e22, + 0x1e2300001e24, + 0x1e2500001e26, + 0x1e2700001e28, + 0x1e2900001e2a, + 0x1e2b00001e2c, + 0x1e2d00001e2e, + 0x1e2f00001e30, + 0x1e3100001e32, + 0x1e3300001e34, + 0x1e3500001e36, + 0x1e3700001e38, + 0x1e3900001e3a, + 0x1e3b00001e3c, + 0x1e3d00001e3e, + 0x1e3f00001e40, + 0x1e4100001e42, + 0x1e4300001e44, + 0x1e4500001e46, + 0x1e4700001e48, + 0x1e4900001e4a, + 0x1e4b00001e4c, + 0x1e4d00001e4e, + 0x1e4f00001e50, + 0x1e5100001e52, + 0x1e5300001e54, + 0x1e5500001e56, + 0x1e5700001e58, + 0x1e5900001e5a, + 0x1e5b00001e5c, + 0x1e5d00001e5e, + 0x1e5f00001e60, + 0x1e6100001e62, + 0x1e6300001e64, + 0x1e6500001e66, + 0x1e6700001e68, + 0x1e6900001e6a, + 0x1e6b00001e6c, + 0x1e6d00001e6e, + 0x1e6f00001e70, + 0x1e7100001e72, + 0x1e7300001e74, + 0x1e7500001e76, + 0x1e7700001e78, + 0x1e7900001e7a, + 0x1e7b00001e7c, + 0x1e7d00001e7e, + 0x1e7f00001e80, + 0x1e8100001e82, + 0x1e8300001e84, + 0x1e8500001e86, + 0x1e8700001e88, + 0x1e8900001e8a, + 0x1e8b00001e8c, + 0x1e8d00001e8e, + 0x1e8f00001e90, + 0x1e9100001e92, + 0x1e9300001e94, + 0x1e9500001e9a, + 0x1e9c00001e9e, + 0x1e9f00001ea0, + 0x1ea100001ea2, + 0x1ea300001ea4, + 0x1ea500001ea6, + 0x1ea700001ea8, + 0x1ea900001eaa, + 0x1eab00001eac, + 0x1ead00001eae, + 0x1eaf00001eb0, + 0x1eb100001eb2, + 0x1eb300001eb4, + 0x1eb500001eb6, + 0x1eb700001eb8, + 0x1eb900001eba, + 0x1ebb00001ebc, + 0x1ebd00001ebe, + 0x1ebf00001ec0, + 0x1ec100001ec2, + 0x1ec300001ec4, + 0x1ec500001ec6, + 0x1ec700001ec8, + 0x1ec900001eca, + 0x1ecb00001ecc, + 0x1ecd00001ece, + 0x1ecf00001ed0, + 0x1ed100001ed2, + 0x1ed300001ed4, + 0x1ed500001ed6, + 0x1ed700001ed8, + 0x1ed900001eda, + 0x1edb00001edc, + 0x1edd00001ede, + 0x1edf00001ee0, + 0x1ee100001ee2, + 0x1ee300001ee4, + 0x1ee500001ee6, + 0x1ee700001ee8, + 0x1ee900001eea, + 0x1eeb00001eec, + 0x1eed00001eee, + 0x1eef00001ef0, + 0x1ef100001ef2, + 0x1ef300001ef4, + 0x1ef500001ef6, + 0x1ef700001ef8, + 0x1ef900001efa, + 0x1efb00001efc, + 0x1efd00001efe, + 0x1eff00001f08, + 0x1f1000001f16, + 0x1f2000001f28, + 0x1f3000001f38, + 0x1f4000001f46, + 0x1f5000001f58, + 0x1f6000001f68, + 0x1f7000001f71, + 0x1f7200001f73, + 0x1f7400001f75, + 0x1f7600001f77, + 0x1f7800001f79, + 0x1f7a00001f7b, + 0x1f7c00001f7d, + 0x1fb000001fb2, + 0x1fb600001fb7, + 0x1fc600001fc7, + 0x1fd000001fd3, + 0x1fd600001fd8, + 0x1fe000001fe3, + 0x1fe400001fe8, + 0x1ff600001ff7, + 0x214e0000214f, + 0x218400002185, + 0x2c3000002c5f, + 0x2c6100002c62, + 0x2c6500002c67, + 0x2c6800002c69, + 0x2c6a00002c6b, + 0x2c6c00002c6d, + 0x2c7100002c72, + 0x2c7300002c75, + 0x2c7600002c7c, + 0x2c8100002c82, + 0x2c8300002c84, + 0x2c8500002c86, + 0x2c8700002c88, + 0x2c8900002c8a, + 0x2c8b00002c8c, + 0x2c8d00002c8e, + 0x2c8f00002c90, + 0x2c9100002c92, + 0x2c9300002c94, + 0x2c9500002c96, + 0x2c9700002c98, + 0x2c9900002c9a, + 0x2c9b00002c9c, + 0x2c9d00002c9e, + 0x2c9f00002ca0, + 0x2ca100002ca2, + 0x2ca300002ca4, + 0x2ca500002ca6, + 0x2ca700002ca8, + 0x2ca900002caa, + 0x2cab00002cac, + 0x2cad00002cae, + 0x2caf00002cb0, + 0x2cb100002cb2, + 0x2cb300002cb4, + 0x2cb500002cb6, + 0x2cb700002cb8, + 0x2cb900002cba, + 0x2cbb00002cbc, + 0x2cbd00002cbe, + 0x2cbf00002cc0, + 0x2cc100002cc2, + 0x2cc300002cc4, + 0x2cc500002cc6, + 0x2cc700002cc8, + 0x2cc900002cca, + 0x2ccb00002ccc, + 0x2ccd00002cce, + 0x2ccf00002cd0, + 0x2cd100002cd2, + 0x2cd300002cd4, + 0x2cd500002cd6, + 0x2cd700002cd8, + 0x2cd900002cda, + 0x2cdb00002cdc, + 0x2cdd00002cde, + 0x2cdf00002ce0, + 0x2ce100002ce2, + 0x2ce300002ce5, + 0x2cec00002ced, + 0x2cee00002cf2, + 0x2cf300002cf4, + 0x2d0000002d26, + 0x2d2700002d28, + 0x2d2d00002d2e, + 0x2d3000002d68, + 0x2d7f00002d97, + 0x2da000002da7, + 0x2da800002daf, + 0x2db000002db7, + 0x2db800002dbf, + 0x2dc000002dc7, + 0x2dc800002dcf, + 0x2dd000002dd7, + 0x2dd800002ddf, + 0x2de000002e00, + 0x2e2f00002e30, + 0x300500003008, + 0x302a0000302e, + 0x303c0000303d, + 0x304100003097, + 0x30990000309b, + 0x309d0000309f, + 0x30a1000030fb, + 0x30fc000030ff, + 0x310500003130, + 0x31a0000031bb, + 0x31f000003200, + 0x340000004db6, + 0x4e0000009ff0, + 0xa0000000a48d, + 0xa4d00000a4fe, + 0xa5000000a60d, + 0xa6100000a62c, + 0xa6410000a642, + 0xa6430000a644, + 0xa6450000a646, + 0xa6470000a648, + 0xa6490000a64a, + 0xa64b0000a64c, + 0xa64d0000a64e, + 0xa64f0000a650, + 0xa6510000a652, + 0xa6530000a654, + 0xa6550000a656, + 0xa6570000a658, + 0xa6590000a65a, + 0xa65b0000a65c, + 0xa65d0000a65e, + 0xa65f0000a660, + 0xa6610000a662, + 0xa6630000a664, + 0xa6650000a666, + 0xa6670000a668, + 0xa6690000a66a, + 0xa66b0000a66c, + 0xa66d0000a670, + 0xa6740000a67e, + 0xa67f0000a680, + 0xa6810000a682, + 0xa6830000a684, + 0xa6850000a686, + 0xa6870000a688, + 0xa6890000a68a, + 0xa68b0000a68c, + 0xa68d0000a68e, + 0xa68f0000a690, + 0xa6910000a692, + 0xa6930000a694, + 0xa6950000a696, + 0xa6970000a698, + 0xa6990000a69a, + 0xa69b0000a69c, + 0xa69e0000a6e6, + 0xa6f00000a6f2, + 0xa7170000a720, + 0xa7230000a724, + 0xa7250000a726, + 0xa7270000a728, + 0xa7290000a72a, + 0xa72b0000a72c, + 0xa72d0000a72e, + 0xa72f0000a732, + 0xa7330000a734, + 0xa7350000a736, + 0xa7370000a738, + 0xa7390000a73a, + 0xa73b0000a73c, + 0xa73d0000a73e, + 0xa73f0000a740, + 0xa7410000a742, + 0xa7430000a744, + 0xa7450000a746, + 0xa7470000a748, + 0xa7490000a74a, + 0xa74b0000a74c, + 0xa74d0000a74e, + 0xa74f0000a750, + 0xa7510000a752, + 0xa7530000a754, + 0xa7550000a756, + 0xa7570000a758, + 0xa7590000a75a, + 0xa75b0000a75c, + 0xa75d0000a75e, + 0xa75f0000a760, + 0xa7610000a762, + 0xa7630000a764, + 0xa7650000a766, + 0xa7670000a768, + 0xa7690000a76a, + 0xa76b0000a76c, + 0xa76d0000a76e, + 0xa76f0000a770, + 0xa7710000a779, + 0xa77a0000a77b, + 0xa77c0000a77d, + 0xa77f0000a780, + 0xa7810000a782, + 0xa7830000a784, + 0xa7850000a786, + 0xa7870000a789, + 0xa78c0000a78d, + 0xa78e0000a790, + 0xa7910000a792, + 0xa7930000a796, + 0xa7970000a798, + 0xa7990000a79a, + 0xa79b0000a79c, + 0xa79d0000a79e, + 0xa79f0000a7a0, + 0xa7a10000a7a2, + 0xa7a30000a7a4, + 0xa7a50000a7a6, + 0xa7a70000a7a8, + 0xa7a90000a7aa, + 0xa7af0000a7b0, + 0xa7b50000a7b6, + 0xa7b70000a7b8, + 0xa7b90000a7ba, + 0xa7f70000a7f8, + 0xa7fa0000a828, + 0xa8400000a874, + 0xa8800000a8c6, + 0xa8d00000a8da, + 0xa8e00000a8f8, + 0xa8fb0000a8fc, + 0xa8fd0000a92e, + 0xa9300000a954, + 0xa9800000a9c1, + 0xa9cf0000a9da, + 0xa9e00000a9ff, + 0xaa000000aa37, + 0xaa400000aa4e, + 0xaa500000aa5a, + 0xaa600000aa77, + 0xaa7a0000aac3, + 0xaadb0000aade, + 0xaae00000aaf0, + 0xaaf20000aaf7, + 0xab010000ab07, + 0xab090000ab0f, + 0xab110000ab17, + 0xab200000ab27, + 0xab280000ab2f, + 0xab300000ab5b, + 0xab600000ab66, + 0xabc00000abeb, + 0xabec0000abee, + 0xabf00000abfa, + 0xac000000d7a4, + 0xfa0e0000fa10, + 0xfa110000fa12, + 0xfa130000fa15, + 0xfa1f0000fa20, + 0xfa210000fa22, + 0xfa230000fa25, + 0xfa270000fa2a, + 0xfb1e0000fb1f, + 0xfe200000fe30, + 0xfe730000fe74, + 0x100000001000c, + 0x1000d00010027, + 0x100280001003b, + 0x1003c0001003e, + 0x1003f0001004e, + 0x100500001005e, + 0x10080000100fb, + 0x101fd000101fe, + 0x102800001029d, + 0x102a0000102d1, + 0x102e0000102e1, + 0x1030000010320, + 0x1032d00010341, + 0x103420001034a, + 0x103500001037b, + 0x103800001039e, + 0x103a0000103c4, + 0x103c8000103d0, + 0x104280001049e, + 0x104a0000104aa, + 0x104d8000104fc, + 0x1050000010528, + 0x1053000010564, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1080000010806, + 0x1080800010809, + 0x1080a00010836, + 0x1083700010839, + 0x1083c0001083d, + 0x1083f00010856, + 0x1086000010877, + 0x108800001089f, + 0x108e0000108f3, + 0x108f4000108f6, + 0x1090000010916, + 0x109200001093a, + 0x10980000109b8, + 0x109be000109c0, + 0x10a0000010a04, + 0x10a0500010a07, + 0x10a0c00010a14, + 0x10a1500010a18, + 0x10a1900010a36, + 0x10a3800010a3b, + 0x10a3f00010a40, + 0x10a6000010a7d, + 0x10a8000010a9d, + 0x10ac000010ac8, + 0x10ac900010ae7, + 0x10b0000010b36, + 0x10b4000010b56, + 0x10b6000010b73, + 0x10b8000010b92, + 0x10c0000010c49, + 0x10cc000010cf3, + 0x10d0000010d28, + 0x10d3000010d3a, + 0x10f0000010f1d, + 0x10f2700010f28, + 0x10f3000010f51, + 0x1100000011047, + 0x1106600011070, + 0x1107f000110bb, + 0x110d0000110e9, + 0x110f0000110fa, + 0x1110000011135, + 0x1113600011140, + 0x1114400011147, + 0x1115000011174, + 0x1117600011177, + 0x11180000111c5, + 0x111c9000111cd, + 0x111d0000111db, + 0x111dc000111dd, + 0x1120000011212, + 0x1121300011238, + 0x1123e0001123f, + 0x1128000011287, + 0x1128800011289, + 0x1128a0001128e, + 0x1128f0001129e, + 0x1129f000112a9, + 0x112b0000112eb, + 0x112f0000112fa, + 0x1130000011304, + 0x113050001130d, + 0x1130f00011311, + 0x1131300011329, + 0x1132a00011331, + 0x1133200011334, + 0x113350001133a, + 0x1133b00011345, + 0x1134700011349, + 0x1134b0001134e, + 0x1135000011351, + 0x1135700011358, + 0x1135d00011364, + 0x113660001136d, + 0x1137000011375, + 0x114000001144b, + 0x114500001145a, + 0x1145e0001145f, + 0x11480000114c6, + 0x114c7000114c8, + 0x114d0000114da, + 0x11580000115b6, + 0x115b8000115c1, + 0x115d8000115de, + 0x1160000011641, + 0x1164400011645, + 0x116500001165a, + 0x11680000116b8, + 0x116c0000116ca, + 0x117000001171b, + 0x1171d0001172c, + 0x117300001173a, + 0x118000001183b, + 0x118c0000118ea, + 0x118ff00011900, + 0x11a0000011a3f, + 0x11a4700011a48, + 0x11a5000011a84, + 0x11a8600011a9a, + 0x11a9d00011a9e, + 0x11ac000011af9, + 0x11c0000011c09, + 0x11c0a00011c37, + 0x11c3800011c41, + 0x11c5000011c5a, + 0x11c7200011c90, + 0x11c9200011ca8, + 0x11ca900011cb7, + 0x11d0000011d07, + 0x11d0800011d0a, + 0x11d0b00011d37, + 0x11d3a00011d3b, + 0x11d3c00011d3e, + 0x11d3f00011d48, + 0x11d5000011d5a, + 0x11d6000011d66, + 0x11d6700011d69, + 0x11d6a00011d8f, + 0x11d9000011d92, + 0x11d9300011d99, + 0x11da000011daa, + 0x11ee000011ef7, + 0x120000001239a, + 0x1248000012544, + 0x130000001342f, + 0x1440000014647, + 0x1680000016a39, + 0x16a4000016a5f, + 0x16a6000016a6a, + 0x16ad000016aee, + 0x16af000016af5, + 0x16b0000016b37, + 0x16b4000016b44, + 0x16b5000016b5a, + 0x16b6300016b78, + 0x16b7d00016b90, + 0x16e6000016e80, + 0x16f0000016f45, + 0x16f5000016f7f, + 0x16f8f00016fa0, + 0x16fe000016fe2, + 0x17000000187f2, + 0x1880000018af3, + 0x1b0000001b11f, + 0x1b1700001b2fc, + 0x1bc000001bc6b, + 0x1bc700001bc7d, + 0x1bc800001bc89, + 0x1bc900001bc9a, + 0x1bc9d0001bc9f, + 0x1da000001da37, + 0x1da3b0001da6d, + 0x1da750001da76, + 0x1da840001da85, + 0x1da9b0001daa0, + 0x1daa10001dab0, + 0x1e0000001e007, + 0x1e0080001e019, + 0x1e01b0001e022, + 0x1e0230001e025, + 0x1e0260001e02b, + 0x1e8000001e8c5, + 0x1e8d00001e8d7, + 0x1e9220001e94b, + 0x1e9500001e95a, + 0x200000002a6d7, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + ), + 'CONTEXTJ': ( + 0x200c0000200e, + ), + 'CONTEXTO': ( + 0xb7000000b8, + 0x37500000376, + 0x5f3000005f5, + 0x6600000066a, + 0x6f0000006fa, + 0x30fb000030fc, + ), +} diff --git a/venv/lib/python3.7/site-packages/idna/intranges.py b/venv/lib/python3.7/site-packages/idna/intranges.py new file mode 100644 index 0000000..fa8a735 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/intranges.py @@ -0,0 +1,53 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect + +def intranges_from_list(list_): + """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. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i+1 < len(sorted_list): + if sorted_list[i] == sorted_list[i+1]-1: + continue + current_range = sorted_list[last_write+1:i+1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + +def _encode_range(start, end): + return (start << 32) | end + +def _decode_range(r): + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_, ranges): + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos-1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/venv/lib/python3.7/site-packages/idna/package_data.py b/venv/lib/python3.7/site-packages/idna/package_data.py new file mode 100644 index 0000000..257e898 --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/package_data.py @@ -0,0 +1,2 @@ +__version__ = '2.8' + diff --git a/venv/lib/python3.7/site-packages/idna/uts46data.py b/venv/lib/python3.7/site-packages/idna/uts46data.py new file mode 100644 index 0000000..a68ed4c --- /dev/null +++ b/venv/lib/python3.7/site-packages/idna/uts46data.py @@ -0,0 +1,8205 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "11.0.0" +def _seg_0(): + return [ + (0x0, '3'), + (0x1, '3'), + (0x2, '3'), + (0x3, '3'), + (0x4, '3'), + (0x5, '3'), + (0x6, '3'), + (0x7, '3'), + (0x8, '3'), + (0x9, '3'), + (0xA, '3'), + (0xB, '3'), + (0xC, '3'), + (0xD, '3'), + (0xE, '3'), + (0xF, '3'), + (0x10, '3'), + (0x11, '3'), + (0x12, '3'), + (0x13, '3'), + (0x14, '3'), + (0x15, '3'), + (0x16, '3'), + (0x17, '3'), + (0x18, '3'), + (0x19, '3'), + (0x1A, '3'), + (0x1B, '3'), + (0x1C, '3'), + (0x1D, '3'), + (0x1E, '3'), + (0x1F, '3'), + (0x20, '3'), + (0x21, '3'), + (0x22, '3'), + (0x23, '3'), + (0x24, '3'), + (0x25, '3'), + (0x26, '3'), + (0x27, '3'), + (0x28, '3'), + (0x29, '3'), + (0x2A, '3'), + (0x2B, '3'), + (0x2C, '3'), + (0x2D, 'V'), + (0x2E, 'V'), + (0x2F, '3'), + (0x30, 'V'), + (0x31, 'V'), + (0x32, 'V'), + (0x33, 'V'), + (0x34, 'V'), + (0x35, 'V'), + (0x36, 'V'), + (0x37, 'V'), + (0x38, 'V'), + (0x39, 'V'), + (0x3A, '3'), + (0x3B, '3'), + (0x3C, '3'), + (0x3D, '3'), + (0x3E, '3'), + (0x3F, '3'), + (0x40, '3'), + (0x41, 'M', u'a'), + (0x42, 'M', u'b'), + (0x43, 'M', u'c'), + (0x44, 'M', u'd'), + (0x45, 'M', u'e'), + (0x46, 'M', u'f'), + (0x47, 'M', u'g'), + (0x48, 'M', u'h'), + (0x49, 'M', u'i'), + (0x4A, 'M', u'j'), + (0x4B, 'M', u'k'), + (0x4C, 'M', u'l'), + (0x4D, 'M', u'm'), + (0x4E, 'M', u'n'), + (0x4F, 'M', u'o'), + (0x50, 'M', u'p'), + (0x51, 'M', u'q'), + (0x52, 'M', u'r'), + (0x53, 'M', u's'), + (0x54, 'M', u't'), + (0x55, 'M', u'u'), + (0x56, 'M', u'v'), + (0x57, 'M', u'w'), + (0x58, 'M', u'x'), + (0x59, 'M', u'y'), + (0x5A, 'M', u'z'), + (0x5B, '3'), + (0x5C, '3'), + (0x5D, '3'), + (0x5E, '3'), + (0x5F, '3'), + (0x60, '3'), + (0x61, 'V'), + (0x62, 'V'), + (0x63, 'V'), + ] + +def _seg_1(): + return [ + (0x64, 'V'), + (0x65, 'V'), + (0x66, 'V'), + (0x67, 'V'), + (0x68, 'V'), + (0x69, 'V'), + (0x6A, 'V'), + (0x6B, 'V'), + (0x6C, 'V'), + (0x6D, 'V'), + (0x6E, 'V'), + (0x6F, 'V'), + (0x70, 'V'), + (0x71, 'V'), + (0x72, 'V'), + (0x73, 'V'), + (0x74, 'V'), + (0x75, 'V'), + (0x76, 'V'), + (0x77, 'V'), + (0x78, 'V'), + (0x79, 'V'), + (0x7A, 'V'), + (0x7B, '3'), + (0x7C, '3'), + (0x7D, '3'), + (0x7E, '3'), + (0x7F, '3'), + (0x80, 'X'), + (0x81, 'X'), + (0x82, 'X'), + (0x83, 'X'), + (0x84, 'X'), + (0x85, 'X'), + (0x86, 'X'), + (0x87, 'X'), + (0x88, 'X'), + (0x89, 'X'), + (0x8A, 'X'), + (0x8B, 'X'), + (0x8C, 'X'), + (0x8D, 'X'), + (0x8E, 'X'), + (0x8F, 'X'), + (0x90, 'X'), + (0x91, 'X'), + (0x92, 'X'), + (0x93, 'X'), + (0x94, 'X'), + (0x95, 'X'), + (0x96, 'X'), + (0x97, 'X'), + (0x98, 'X'), + (0x99, 'X'), + (0x9A, 'X'), + (0x9B, 'X'), + (0x9C, 'X'), + (0x9D, 'X'), + (0x9E, 'X'), + (0x9F, 'X'), + (0xA0, '3', u' '), + (0xA1, 'V'), + (0xA2, 'V'), + (0xA3, 'V'), + (0xA4, 'V'), + (0xA5, 'V'), + (0xA6, 'V'), + (0xA7, 'V'), + (0xA8, '3', u' ̈'), + (0xA9, 'V'), + (0xAA, 'M', u'a'), + (0xAB, 'V'), + (0xAC, 'V'), + (0xAD, 'I'), + (0xAE, 'V'), + (0xAF, '3', u' ̄'), + (0xB0, 'V'), + (0xB1, 'V'), + (0xB2, 'M', u'2'), + (0xB3, 'M', u'3'), + (0xB4, '3', u' ́'), + (0xB5, 'M', u'μ'), + (0xB6, 'V'), + (0xB7, 'V'), + (0xB8, '3', u' ̧'), + (0xB9, 'M', u'1'), + (0xBA, 'M', u'o'), + (0xBB, 'V'), + (0xBC, 'M', u'1⁄4'), + (0xBD, 'M', u'1⁄2'), + (0xBE, 'M', u'3⁄4'), + (0xBF, 'V'), + (0xC0, 'M', u'à'), + (0xC1, 'M', u'á'), + (0xC2, 'M', u'â'), + (0xC3, 'M', u'ã'), + (0xC4, 'M', u'ä'), + (0xC5, 'M', u'å'), + (0xC6, 'M', u'æ'), + (0xC7, 'M', u'ç'), + ] + +def _seg_2(): + return [ + (0xC8, 'M', u'è'), + (0xC9, 'M', u'é'), + (0xCA, 'M', u'ê'), + (0xCB, 'M', u'ë'), + (0xCC, 'M', u'ì'), + (0xCD, 'M', u'í'), + (0xCE, 'M', u'î'), + (0xCF, 'M', u'ï'), + (0xD0, 'M', u'ð'), + (0xD1, 'M', u'ñ'), + (0xD2, 'M', u'ò'), + (0xD3, 'M', u'ó'), + (0xD4, 'M', u'ô'), + (0xD5, 'M', u'õ'), + (0xD6, 'M', u'ö'), + (0xD7, 'V'), + (0xD8, 'M', u'ø'), + (0xD9, 'M', u'ù'), + (0xDA, 'M', u'ú'), + (0xDB, 'M', u'û'), + (0xDC, 'M', u'ü'), + (0xDD, 'M', u'ý'), + (0xDE, 'M', u'þ'), + (0xDF, 'D', u'ss'), + (0xE0, 'V'), + (0xE1, 'V'), + (0xE2, 'V'), + (0xE3, 'V'), + (0xE4, 'V'), + (0xE5, 'V'), + (0xE6, 'V'), + (0xE7, 'V'), + (0xE8, 'V'), + (0xE9, 'V'), + (0xEA, 'V'), + (0xEB, 'V'), + (0xEC, 'V'), + (0xED, 'V'), + (0xEE, 'V'), + (0xEF, 'V'), + (0xF0, 'V'), + (0xF1, 'V'), + (0xF2, 'V'), + (0xF3, 'V'), + (0xF4, 'V'), + (0xF5, 'V'), + (0xF6, 'V'), + (0xF7, 'V'), + (0xF8, 'V'), + (0xF9, 'V'), + (0xFA, 'V'), + (0xFB, 'V'), + (0xFC, 'V'), + (0xFD, 'V'), + (0xFE, 'V'), + (0xFF, 'V'), + (0x100, 'M', u'ā'), + (0x101, 'V'), + (0x102, 'M', u'ă'), + (0x103, 'V'), + (0x104, 'M', u'ą'), + (0x105, 'V'), + (0x106, 'M', u'ć'), + (0x107, 'V'), + (0x108, 'M', u'ĉ'), + (0x109, 'V'), + (0x10A, 'M', u'ċ'), + (0x10B, 'V'), + (0x10C, 'M', u'č'), + (0x10D, 'V'), + (0x10E, 'M', u'ď'), + (0x10F, 'V'), + (0x110, 'M', u'đ'), + (0x111, 'V'), + (0x112, 'M', u'ē'), + (0x113, 'V'), + (0x114, 'M', u'ĕ'), + (0x115, 'V'), + (0x116, 'M', u'ė'), + (0x117, 'V'), + (0x118, 'M', u'ę'), + (0x119, 'V'), + (0x11A, 'M', u'ě'), + (0x11B, 'V'), + (0x11C, 'M', u'ĝ'), + (0x11D, 'V'), + (0x11E, 'M', u'ğ'), + (0x11F, 'V'), + (0x120, 'M', u'ġ'), + (0x121, 'V'), + (0x122, 'M', u'ģ'), + (0x123, 'V'), + (0x124, 'M', u'ĥ'), + (0x125, 'V'), + (0x126, 'M', u'ħ'), + (0x127, 'V'), + (0x128, 'M', u'ĩ'), + (0x129, 'V'), + (0x12A, 'M', u'ī'), + (0x12B, 'V'), + ] + +def _seg_3(): + return [ + (0x12C, 'M', u'ĭ'), + (0x12D, 'V'), + (0x12E, 'M', u'į'), + (0x12F, 'V'), + (0x130, 'M', u'i̇'), + (0x131, 'V'), + (0x132, 'M', u'ij'), + (0x134, 'M', u'ĵ'), + (0x135, 'V'), + (0x136, 'M', u'ķ'), + (0x137, 'V'), + (0x139, 'M', u'ĺ'), + (0x13A, 'V'), + (0x13B, 'M', u'ļ'), + (0x13C, 'V'), + (0x13D, 'M', u'ľ'), + (0x13E, 'V'), + (0x13F, 'M', u'l·'), + (0x141, 'M', u'ł'), + (0x142, 'V'), + (0x143, 'M', u'ń'), + (0x144, 'V'), + (0x145, 'M', u'ņ'), + (0x146, 'V'), + (0x147, 'M', u'ň'), + (0x148, 'V'), + (0x149, 'M', u'ʼn'), + (0x14A, 'M', u'ŋ'), + (0x14B, 'V'), + (0x14C, 'M', u'ō'), + (0x14D, 'V'), + (0x14E, 'M', u'ŏ'), + (0x14F, 'V'), + (0x150, 'M', u'ő'), + (0x151, 'V'), + (0x152, 'M', u'œ'), + (0x153, 'V'), + (0x154, 'M', u'ŕ'), + (0x155, 'V'), + (0x156, 'M', u'ŗ'), + (0x157, 'V'), + (0x158, 'M', u'ř'), + (0x159, 'V'), + (0x15A, 'M', u'ś'), + (0x15B, 'V'), + (0x15C, 'M', u'ŝ'), + (0x15D, 'V'), + (0x15E, 'M', u'ş'), + (0x15F, 'V'), + (0x160, 'M', u'š'), + (0x161, 'V'), + (0x162, 'M', u'ţ'), + (0x163, 'V'), + (0x164, 'M', u'ť'), + (0x165, 'V'), + (0x166, 'M', u'ŧ'), + (0x167, 'V'), + (0x168, 'M', u'ũ'), + (0x169, 'V'), + (0x16A, 'M', u'ū'), + (0x16B, 'V'), + (0x16C, 'M', u'ŭ'), + (0x16D, 'V'), + (0x16E, 'M', u'ů'), + (0x16F, 'V'), + (0x170, 'M', u'ű'), + (0x171, 'V'), + (0x172, 'M', u'ų'), + (0x173, 'V'), + (0x174, 'M', u'ŵ'), + (0x175, 'V'), + (0x176, 'M', u'ŷ'), + (0x177, 'V'), + (0x178, 'M', u'ÿ'), + (0x179, 'M', u'ź'), + (0x17A, 'V'), + (0x17B, 'M', u'ż'), + (0x17C, 'V'), + (0x17D, 'M', u'ž'), + (0x17E, 'V'), + (0x17F, 'M', u's'), + (0x180, 'V'), + (0x181, 'M', u'ɓ'), + (0x182, 'M', u'ƃ'), + (0x183, 'V'), + (0x184, 'M', u'ƅ'), + (0x185, 'V'), + (0x186, 'M', u'ɔ'), + (0x187, 'M', u'ƈ'), + (0x188, 'V'), + (0x189, 'M', u'ɖ'), + (0x18A, 'M', u'ɗ'), + (0x18B, 'M', u'ƌ'), + (0x18C, 'V'), + (0x18E, 'M', u'ǝ'), + (0x18F, 'M', u'ə'), + (0x190, 'M', u'ɛ'), + (0x191, 'M', u'ƒ'), + (0x192, 'V'), + (0x193, 'M', u'ɠ'), + ] + +def _seg_4(): + return [ + (0x194, 'M', u'ɣ'), + (0x195, 'V'), + (0x196, 'M', u'ɩ'), + (0x197, 'M', u'ɨ'), + (0x198, 'M', u'ƙ'), + (0x199, 'V'), + (0x19C, 'M', u'ɯ'), + (0x19D, 'M', u'ɲ'), + (0x19E, 'V'), + (0x19F, 'M', u'ɵ'), + (0x1A0, 'M', u'ơ'), + (0x1A1, 'V'), + (0x1A2, 'M', u'ƣ'), + (0x1A3, 'V'), + (0x1A4, 'M', u'ƥ'), + (0x1A5, 'V'), + (0x1A6, 'M', u'ʀ'), + (0x1A7, 'M', u'ƨ'), + (0x1A8, 'V'), + (0x1A9, 'M', u'ʃ'), + (0x1AA, 'V'), + (0x1AC, 'M', u'ƭ'), + (0x1AD, 'V'), + (0x1AE, 'M', u'ʈ'), + (0x1AF, 'M', u'ư'), + (0x1B0, 'V'), + (0x1B1, 'M', u'ʊ'), + (0x1B2, 'M', u'ʋ'), + (0x1B3, 'M', u'ƴ'), + (0x1B4, 'V'), + (0x1B5, 'M', u'ƶ'), + (0x1B6, 'V'), + (0x1B7, 'M', u'ʒ'), + (0x1B8, 'M', u'ƹ'), + (0x1B9, 'V'), + (0x1BC, 'M', u'ƽ'), + (0x1BD, 'V'), + (0x1C4, 'M', u'dž'), + (0x1C7, 'M', u'lj'), + (0x1CA, 'M', u'nj'), + (0x1CD, 'M', u'ǎ'), + (0x1CE, 'V'), + (0x1CF, 'M', u'ǐ'), + (0x1D0, 'V'), + (0x1D1, 'M', u'ǒ'), + (0x1D2, 'V'), + (0x1D3, 'M', u'ǔ'), + (0x1D4, 'V'), + (0x1D5, 'M', u'ǖ'), + (0x1D6, 'V'), + (0x1D7, 'M', u'ǘ'), + (0x1D8, 'V'), + (0x1D9, 'M', u'ǚ'), + (0x1DA, 'V'), + (0x1DB, 'M', u'ǜ'), + (0x1DC, 'V'), + (0x1DE, 'M', u'ǟ'), + (0x1DF, 'V'), + (0x1E0, 'M', u'ǡ'), + (0x1E1, 'V'), + (0x1E2, 'M', u'ǣ'), + (0x1E3, 'V'), + (0x1E4, 'M', u'ǥ'), + (0x1E5, 'V'), + (0x1E6, 'M', u'ǧ'), + (0x1E7, 'V'), + (0x1E8, 'M', u'ǩ'), + (0x1E9, 'V'), + (0x1EA, 'M', u'ǫ'), + (0x1EB, 'V'), + (0x1EC, 'M', u'ǭ'), + (0x1ED, 'V'), + (0x1EE, 'M', u'ǯ'), + (0x1EF, 'V'), + (0x1F1, 'M', u'dz'), + (0x1F4, 'M', u'ǵ'), + (0x1F5, 'V'), + (0x1F6, 'M', u'ƕ'), + (0x1F7, 'M', u'ƿ'), + (0x1F8, 'M', u'ǹ'), + (0x1F9, 'V'), + (0x1FA, 'M', u'ǻ'), + (0x1FB, 'V'), + (0x1FC, 'M', u'ǽ'), + (0x1FD, 'V'), + (0x1FE, 'M', u'ǿ'), + (0x1FF, 'V'), + (0x200, 'M', u'ȁ'), + (0x201, 'V'), + (0x202, 'M', u'ȃ'), + (0x203, 'V'), + (0x204, 'M', u'ȅ'), + (0x205, 'V'), + (0x206, 'M', u'ȇ'), + (0x207, 'V'), + (0x208, 'M', u'ȉ'), + (0x209, 'V'), + (0x20A, 'M', u'ȋ'), + (0x20B, 'V'), + (0x20C, 'M', u'ȍ'), + ] + +def _seg_5(): + return [ + (0x20D, 'V'), + (0x20E, 'M', u'ȏ'), + (0x20F, 'V'), + (0x210, 'M', u'ȑ'), + (0x211, 'V'), + (0x212, 'M', u'ȓ'), + (0x213, 'V'), + (0x214, 'M', u'ȕ'), + (0x215, 'V'), + (0x216, 'M', u'ȗ'), + (0x217, 'V'), + (0x218, 'M', u'ș'), + (0x219, 'V'), + (0x21A, 'M', u'ț'), + (0x21B, 'V'), + (0x21C, 'M', u'ȝ'), + (0x21D, 'V'), + (0x21E, 'M', u'ȟ'), + (0x21F, 'V'), + (0x220, 'M', u'ƞ'), + (0x221, 'V'), + (0x222, 'M', u'ȣ'), + (0x223, 'V'), + (0x224, 'M', u'ȥ'), + (0x225, 'V'), + (0x226, 'M', u'ȧ'), + (0x227, 'V'), + (0x228, 'M', u'ȩ'), + (0x229, 'V'), + (0x22A, 'M', u'ȫ'), + (0x22B, 'V'), + (0x22C, 'M', u'ȭ'), + (0x22D, 'V'), + (0x22E, 'M', u'ȯ'), + (0x22F, 'V'), + (0x230, 'M', u'ȱ'), + (0x231, 'V'), + (0x232, 'M', u'ȳ'), + (0x233, 'V'), + (0x23A, 'M', u'ⱥ'), + (0x23B, 'M', u'ȼ'), + (0x23C, 'V'), + (0x23D, 'M', u'ƚ'), + (0x23E, 'M', u'ⱦ'), + (0x23F, 'V'), + (0x241, 'M', u'ɂ'), + (0x242, 'V'), + (0x243, 'M', u'ƀ'), + (0x244, 'M', u'ʉ'), + (0x245, 'M', u'ʌ'), + (0x246, 'M', u'ɇ'), + (0x247, 'V'), + (0x248, 'M', u'ɉ'), + (0x249, 'V'), + (0x24A, 'M', u'ɋ'), + (0x24B, 'V'), + (0x24C, 'M', u'ɍ'), + (0x24D, 'V'), + (0x24E, 'M', u'ɏ'), + (0x24F, 'V'), + (0x2B0, 'M', u'h'), + (0x2B1, 'M', u'ɦ'), + (0x2B2, 'M', u'j'), + (0x2B3, 'M', u'r'), + (0x2B4, 'M', u'ɹ'), + (0x2B5, 'M', u'ɻ'), + (0x2B6, 'M', u'ʁ'), + (0x2B7, 'M', u'w'), + (0x2B8, 'M', u'y'), + (0x2B9, 'V'), + (0x2D8, '3', u' ̆'), + (0x2D9, '3', u' ̇'), + (0x2DA, '3', u' ̊'), + (0x2DB, '3', u' ̨'), + (0x2DC, '3', u' ̃'), + (0x2DD, '3', u' ̋'), + (0x2DE, 'V'), + (0x2E0, 'M', u'ɣ'), + (0x2E1, 'M', u'l'), + (0x2E2, 'M', u's'), + (0x2E3, 'M', u'x'), + (0x2E4, 'M', u'ʕ'), + (0x2E5, 'V'), + (0x340, 'M', u'̀'), + (0x341, 'M', u'́'), + (0x342, 'V'), + (0x343, 'M', u'̓'), + (0x344, 'M', u'̈́'), + (0x345, 'M', u'ι'), + (0x346, 'V'), + (0x34F, 'I'), + (0x350, 'V'), + (0x370, 'M', u'ͱ'), + (0x371, 'V'), + (0x372, 'M', u'ͳ'), + (0x373, 'V'), + (0x374, 'M', u'ʹ'), + (0x375, 'V'), + (0x376, 'M', u'ͷ'), + (0x377, 'V'), + ] + +def _seg_6(): + return [ + (0x378, 'X'), + (0x37A, '3', u' ι'), + (0x37B, 'V'), + (0x37E, '3', u';'), + (0x37F, 'M', u'ϳ'), + (0x380, 'X'), + (0x384, '3', u' ́'), + (0x385, '3', u' ̈́'), + (0x386, 'M', u'ά'), + (0x387, 'M', u'·'), + (0x388, 'M', u'έ'), + (0x389, 'M', u'ή'), + (0x38A, 'M', u'ί'), + (0x38B, 'X'), + (0x38C, 'M', u'ό'), + (0x38D, 'X'), + (0x38E, 'M', u'ύ'), + (0x38F, 'M', u'ώ'), + (0x390, 'V'), + (0x391, 'M', u'α'), + (0x392, 'M', u'β'), + (0x393, 'M', u'γ'), + (0x394, 'M', u'δ'), + (0x395, 'M', u'ε'), + (0x396, 'M', u'ζ'), + (0x397, 'M', u'η'), + (0x398, 'M', u'θ'), + (0x399, 'M', u'ι'), + (0x39A, 'M', u'κ'), + (0x39B, 'M', u'λ'), + (0x39C, 'M', u'μ'), + (0x39D, 'M', u'ν'), + (0x39E, 'M', u'ξ'), + (0x39F, 'M', u'ο'), + (0x3A0, 'M', u'π'), + (0x3A1, 'M', u'ρ'), + (0x3A2, 'X'), + (0x3A3, 'M', u'σ'), + (0x3A4, 'M', u'τ'), + (0x3A5, 'M', u'υ'), + (0x3A6, 'M', u'φ'), + (0x3A7, 'M', u'χ'), + (0x3A8, 'M', u'ψ'), + (0x3A9, 'M', u'ω'), + (0x3AA, 'M', u'ϊ'), + (0x3AB, 'M', u'ϋ'), + (0x3AC, 'V'), + (0x3C2, 'D', u'σ'), + (0x3C3, 'V'), + (0x3CF, 'M', u'ϗ'), + (0x3D0, 'M', u'β'), + (0x3D1, 'M', u'θ'), + (0x3D2, 'M', u'υ'), + (0x3D3, 'M', u'ύ'), + (0x3D4, 'M', u'ϋ'), + (0x3D5, 'M', u'φ'), + (0x3D6, 'M', u'π'), + (0x3D7, 'V'), + (0x3D8, 'M', u'ϙ'), + (0x3D9, 'V'), + (0x3DA, 'M', u'ϛ'), + (0x3DB, 'V'), + (0x3DC, 'M', u'ϝ'), + (0x3DD, 'V'), + (0x3DE, 'M', u'ϟ'), + (0x3DF, 'V'), + (0x3E0, 'M', u'ϡ'), + (0x3E1, 'V'), + (0x3E2, 'M', u'ϣ'), + (0x3E3, 'V'), + (0x3E4, 'M', u'ϥ'), + (0x3E5, 'V'), + (0x3E6, 'M', u'ϧ'), + (0x3E7, 'V'), + (0x3E8, 'M', u'ϩ'), + (0x3E9, 'V'), + (0x3EA, 'M', u'ϫ'), + (0x3EB, 'V'), + (0x3EC, 'M', u'ϭ'), + (0x3ED, 'V'), + (0x3EE, 'M', u'ϯ'), + (0x3EF, 'V'), + (0x3F0, 'M', u'κ'), + (0x3F1, 'M', u'ρ'), + (0x3F2, 'M', u'σ'), + (0x3F3, 'V'), + (0x3F4, 'M', u'θ'), + (0x3F5, 'M', u'ε'), + (0x3F6, 'V'), + (0x3F7, 'M', u'ϸ'), + (0x3F8, 'V'), + (0x3F9, 'M', u'σ'), + (0x3FA, 'M', u'ϻ'), + (0x3FB, 'V'), + (0x3FD, 'M', u'ͻ'), + (0x3FE, 'M', u'ͼ'), + (0x3FF, 'M', u'ͽ'), + (0x400, 'M', u'ѐ'), + (0x401, 'M', u'ё'), + (0x402, 'M', u'ђ'), + ] + +def _seg_7(): + return [ + (0x403, 'M', u'ѓ'), + (0x404, 'M', u'є'), + (0x405, 'M', u'ѕ'), + (0x406, 'M', u'і'), + (0x407, 'M', u'ї'), + (0x408, 'M', u'ј'), + (0x409, 'M', u'љ'), + (0x40A, 'M', u'њ'), + (0x40B, 'M', u'ћ'), + (0x40C, 'M', u'ќ'), + (0x40D, 'M', u'ѝ'), + (0x40E, 'M', u'ў'), + (0x40F, 'M', u'џ'), + (0x410, 'M', u'а'), + (0x411, 'M', u'б'), + (0x412, 'M', u'в'), + (0x413, 'M', u'г'), + (0x414, 'M', u'д'), + (0x415, 'M', u'е'), + (0x416, 'M', u'ж'), + (0x417, 'M', u'з'), + (0x418, 'M', u'и'), + (0x419, 'M', u'й'), + (0x41A, 'M', u'к'), + (0x41B, 'M', u'л'), + (0x41C, 'M', u'м'), + (0x41D, 'M', u'н'), + (0x41E, 'M', u'о'), + (0x41F, 'M', u'п'), + (0x420, 'M', u'р'), + (0x421, 'M', u'с'), + (0x422, 'M', u'т'), + (0x423, 'M', u'у'), + (0x424, 'M', u'ф'), + (0x425, 'M', u'х'), + (0x426, 'M', u'ц'), + (0x427, 'M', u'ч'), + (0x428, 'M', u'ш'), + (0x429, 'M', u'щ'), + (0x42A, 'M', u'ъ'), + (0x42B, 'M', u'ы'), + (0x42C, 'M', u'ь'), + (0x42D, 'M', u'э'), + (0x42E, 'M', u'ю'), + (0x42F, 'M', u'я'), + (0x430, 'V'), + (0x460, 'M', u'ѡ'), + (0x461, 'V'), + (0x462, 'M', u'ѣ'), + (0x463, 'V'), + (0x464, 'M', u'ѥ'), + (0x465, 'V'), + (0x466, 'M', u'ѧ'), + (0x467, 'V'), + (0x468, 'M', u'ѩ'), + (0x469, 'V'), + (0x46A, 'M', u'ѫ'), + (0x46B, 'V'), + (0x46C, 'M', u'ѭ'), + (0x46D, 'V'), + (0x46E, 'M', u'ѯ'), + (0x46F, 'V'), + (0x470, 'M', u'ѱ'), + (0x471, 'V'), + (0x472, 'M', u'ѳ'), + (0x473, 'V'), + (0x474, 'M', u'ѵ'), + (0x475, 'V'), + (0x476, 'M', u'ѷ'), + (0x477, 'V'), + (0x478, 'M', u'ѹ'), + (0x479, 'V'), + (0x47A, 'M', u'ѻ'), + (0x47B, 'V'), + (0x47C, 'M', u'ѽ'), + (0x47D, 'V'), + (0x47E, 'M', u'ѿ'), + (0x47F, 'V'), + (0x480, 'M', u'ҁ'), + (0x481, 'V'), + (0x48A, 'M', u'ҋ'), + (0x48B, 'V'), + (0x48C, 'M', u'ҍ'), + (0x48D, 'V'), + (0x48E, 'M', u'ҏ'), + (0x48F, 'V'), + (0x490, 'M', u'ґ'), + (0x491, 'V'), + (0x492, 'M', u'ғ'), + (0x493, 'V'), + (0x494, 'M', u'ҕ'), + (0x495, 'V'), + (0x496, 'M', u'җ'), + (0x497, 'V'), + (0x498, 'M', u'ҙ'), + (0x499, 'V'), + (0x49A, 'M', u'қ'), + (0x49B, 'V'), + (0x49C, 'M', u'ҝ'), + (0x49D, 'V'), + ] + +def _seg_8(): + return [ + (0x49E, 'M', u'ҟ'), + (0x49F, 'V'), + (0x4A0, 'M', u'ҡ'), + (0x4A1, 'V'), + (0x4A2, 'M', u'ң'), + (0x4A3, 'V'), + (0x4A4, 'M', u'ҥ'), + (0x4A5, 'V'), + (0x4A6, 'M', u'ҧ'), + (0x4A7, 'V'), + (0x4A8, 'M', u'ҩ'), + (0x4A9, 'V'), + (0x4AA, 'M', u'ҫ'), + (0x4AB, 'V'), + (0x4AC, 'M', u'ҭ'), + (0x4AD, 'V'), + (0x4AE, 'M', u'ү'), + (0x4AF, 'V'), + (0x4B0, 'M', u'ұ'), + (0x4B1, 'V'), + (0x4B2, 'M', u'ҳ'), + (0x4B3, 'V'), + (0x4B4, 'M', u'ҵ'), + (0x4B5, 'V'), + (0x4B6, 'M', u'ҷ'), + (0x4B7, 'V'), + (0x4B8, 'M', u'ҹ'), + (0x4B9, 'V'), + (0x4BA, 'M', u'һ'), + (0x4BB, 'V'), + (0x4BC, 'M', u'ҽ'), + (0x4BD, 'V'), + (0x4BE, 'M', u'ҿ'), + (0x4BF, 'V'), + (0x4C0, 'X'), + (0x4C1, 'M', u'ӂ'), + (0x4C2, 'V'), + (0x4C3, 'M', u'ӄ'), + (0x4C4, 'V'), + (0x4C5, 'M', u'ӆ'), + (0x4C6, 'V'), + (0x4C7, 'M', u'ӈ'), + (0x4C8, 'V'), + (0x4C9, 'M', u'ӊ'), + (0x4CA, 'V'), + (0x4CB, 'M', u'ӌ'), + (0x4CC, 'V'), + (0x4CD, 'M', u'ӎ'), + (0x4CE, 'V'), + (0x4D0, 'M', u'ӑ'), + (0x4D1, 'V'), + (0x4D2, 'M', u'ӓ'), + (0x4D3, 'V'), + (0x4D4, 'M', u'ӕ'), + (0x4D5, 'V'), + (0x4D6, 'M', u'ӗ'), + (0x4D7, 'V'), + (0x4D8, 'M', u'ә'), + (0x4D9, 'V'), + (0x4DA, 'M', u'ӛ'), + (0x4DB, 'V'), + (0x4DC, 'M', u'ӝ'), + (0x4DD, 'V'), + (0x4DE, 'M', u'ӟ'), + (0x4DF, 'V'), + (0x4E0, 'M', u'ӡ'), + (0x4E1, 'V'), + (0x4E2, 'M', u'ӣ'), + (0x4E3, 'V'), + (0x4E4, 'M', u'ӥ'), + (0x4E5, 'V'), + (0x4E6, 'M', u'ӧ'), + (0x4E7, 'V'), + (0x4E8, 'M', u'ө'), + (0x4E9, 'V'), + (0x4EA, 'M', u'ӫ'), + (0x4EB, 'V'), + (0x4EC, 'M', u'ӭ'), + (0x4ED, 'V'), + (0x4EE, 'M', u'ӯ'), + (0x4EF, 'V'), + (0x4F0, 'M', u'ӱ'), + (0x4F1, 'V'), + (0x4F2, 'M', u'ӳ'), + (0x4F3, 'V'), + (0x4F4, 'M', u'ӵ'), + (0x4F5, 'V'), + (0x4F6, 'M', u'ӷ'), + (0x4F7, 'V'), + (0x4F8, 'M', u'ӹ'), + (0x4F9, 'V'), + (0x4FA, 'M', u'ӻ'), + (0x4FB, 'V'), + (0x4FC, 'M', u'ӽ'), + (0x4FD, 'V'), + (0x4FE, 'M', u'ӿ'), + (0x4FF, 'V'), + (0x500, 'M', u'ԁ'), + (0x501, 'V'), + (0x502, 'M', u'ԃ'), + ] + +def _seg_9(): + return [ + (0x503, 'V'), + (0x504, 'M', u'ԅ'), + (0x505, 'V'), + (0x506, 'M', u'ԇ'), + (0x507, 'V'), + (0x508, 'M', u'ԉ'), + (0x509, 'V'), + (0x50A, 'M', u'ԋ'), + (0x50B, 'V'), + (0x50C, 'M', u'ԍ'), + (0x50D, 'V'), + (0x50E, 'M', u'ԏ'), + (0x50F, 'V'), + (0x510, 'M', u'ԑ'), + (0x511, 'V'), + (0x512, 'M', u'ԓ'), + (0x513, 'V'), + (0x514, 'M', u'ԕ'), + (0x515, 'V'), + (0x516, 'M', u'ԗ'), + (0x517, 'V'), + (0x518, 'M', u'ԙ'), + (0x519, 'V'), + (0x51A, 'M', u'ԛ'), + (0x51B, 'V'), + (0x51C, 'M', u'ԝ'), + (0x51D, 'V'), + (0x51E, 'M', u'ԟ'), + (0x51F, 'V'), + (0x520, 'M', u'ԡ'), + (0x521, 'V'), + (0x522, 'M', u'ԣ'), + (0x523, 'V'), + (0x524, 'M', u'ԥ'), + (0x525, 'V'), + (0x526, 'M', u'ԧ'), + (0x527, 'V'), + (0x528, 'M', u'ԩ'), + (0x529, 'V'), + (0x52A, 'M', u'ԫ'), + (0x52B, 'V'), + (0x52C, 'M', u'ԭ'), + (0x52D, 'V'), + (0x52E, 'M', u'ԯ'), + (0x52F, 'V'), + (0x530, 'X'), + (0x531, 'M', u'ա'), + (0x532, 'M', u'բ'), + (0x533, 'M', u'գ'), + (0x534, 'M', u'դ'), + (0x535, 'M', u'ե'), + (0x536, 'M', u'զ'), + (0x537, 'M', u'է'), + (0x538, 'M', u'ը'), + (0x539, 'M', u'թ'), + (0x53A, 'M', u'ժ'), + (0x53B, 'M', u'ի'), + (0x53C, 'M', u'լ'), + (0x53D, 'M', u'խ'), + (0x53E, 'M', u'ծ'), + (0x53F, 'M', u'կ'), + (0x540, 'M', u'հ'), + (0x541, 'M', u'ձ'), + (0x542, 'M', u'ղ'), + (0x543, 'M', u'ճ'), + (0x544, 'M', u'մ'), + (0x545, 'M', u'յ'), + (0x546, 'M', u'ն'), + (0x547, 'M', u'շ'), + (0x548, 'M', u'ո'), + (0x549, 'M', u'չ'), + (0x54A, 'M', u'պ'), + (0x54B, 'M', u'ջ'), + (0x54C, 'M', u'ռ'), + (0x54D, 'M', u'ս'), + (0x54E, 'M', u'վ'), + (0x54F, 'M', u'տ'), + (0x550, 'M', u'ր'), + (0x551, 'M', u'ց'), + (0x552, 'M', u'ւ'), + (0x553, 'M', u'փ'), + (0x554, 'M', u'ք'), + (0x555, 'M', u'օ'), + (0x556, 'M', u'ֆ'), + (0x557, 'X'), + (0x559, 'V'), + (0x587, 'M', u'եւ'), + (0x588, 'V'), + (0x58B, 'X'), + (0x58D, 'V'), + (0x590, 'X'), + (0x591, 'V'), + (0x5C8, 'X'), + (0x5D0, 'V'), + (0x5EB, 'X'), + (0x5EF, 'V'), + (0x5F5, 'X'), + (0x606, 'V'), + (0x61C, 'X'), + (0x61E, 'V'), + ] + +def _seg_10(): + return [ + (0x675, 'M', u'اٴ'), + (0x676, 'M', u'وٴ'), + (0x677, 'M', u'ۇٴ'), + (0x678, 'M', u'يٴ'), + (0x679, 'V'), + (0x6DD, 'X'), + (0x6DE, 'V'), + (0x70E, 'X'), + (0x710, 'V'), + (0x74B, 'X'), + (0x74D, 'V'), + (0x7B2, 'X'), + (0x7C0, 'V'), + (0x7FB, 'X'), + (0x7FD, 'V'), + (0x82E, 'X'), + (0x830, 'V'), + (0x83F, 'X'), + (0x840, 'V'), + (0x85C, 'X'), + (0x85E, 'V'), + (0x85F, 'X'), + (0x860, 'V'), + (0x86B, 'X'), + (0x8A0, 'V'), + (0x8B5, 'X'), + (0x8B6, 'V'), + (0x8BE, 'X'), + (0x8D3, 'V'), + (0x8E2, 'X'), + (0x8E3, 'V'), + (0x958, 'M', u'क़'), + (0x959, 'M', u'ख़'), + (0x95A, 'M', u'ग़'), + (0x95B, 'M', u'ज़'), + (0x95C, 'M', u'ड़'), + (0x95D, 'M', u'ढ़'), + (0x95E, 'M', u'फ़'), + (0x95F, 'M', u'य़'), + (0x960, 'V'), + (0x984, 'X'), + (0x985, 'V'), + (0x98D, 'X'), + (0x98F, 'V'), + (0x991, 'X'), + (0x993, 'V'), + (0x9A9, 'X'), + (0x9AA, 'V'), + (0x9B1, 'X'), + (0x9B2, 'V'), + (0x9B3, 'X'), + (0x9B6, 'V'), + (0x9BA, 'X'), + (0x9BC, 'V'), + (0x9C5, 'X'), + (0x9C7, 'V'), + (0x9C9, 'X'), + (0x9CB, 'V'), + (0x9CF, 'X'), + (0x9D7, 'V'), + (0x9D8, 'X'), + (0x9DC, 'M', u'ড়'), + (0x9DD, 'M', u'ঢ়'), + (0x9DE, 'X'), + (0x9DF, 'M', u'য়'), + (0x9E0, 'V'), + (0x9E4, 'X'), + (0x9E6, 'V'), + (0x9FF, 'X'), + (0xA01, 'V'), + (0xA04, 'X'), + (0xA05, 'V'), + (0xA0B, 'X'), + (0xA0F, 'V'), + (0xA11, 'X'), + (0xA13, 'V'), + (0xA29, 'X'), + (0xA2A, 'V'), + (0xA31, 'X'), + (0xA32, 'V'), + (0xA33, 'M', u'ਲ਼'), + (0xA34, 'X'), + (0xA35, 'V'), + (0xA36, 'M', u'ਸ਼'), + (0xA37, 'X'), + (0xA38, 'V'), + (0xA3A, 'X'), + (0xA3C, 'V'), + (0xA3D, 'X'), + (0xA3E, 'V'), + (0xA43, 'X'), + (0xA47, 'V'), + (0xA49, 'X'), + (0xA4B, 'V'), + (0xA4E, 'X'), + (0xA51, 'V'), + (0xA52, 'X'), + (0xA59, 'M', u'ਖ਼'), + (0xA5A, 'M', u'ਗ਼'), + (0xA5B, 'M', u'ਜ਼'), + ] + +def _seg_11(): + return [ + (0xA5C, 'V'), + (0xA5D, 'X'), + (0xA5E, 'M', u'ਫ਼'), + (0xA5F, 'X'), + (0xA66, 'V'), + (0xA77, 'X'), + (0xA81, 'V'), + (0xA84, 'X'), + (0xA85, 'V'), + (0xA8E, 'X'), + (0xA8F, 'V'), + (0xA92, 'X'), + (0xA93, 'V'), + (0xAA9, 'X'), + (0xAAA, 'V'), + (0xAB1, 'X'), + (0xAB2, 'V'), + (0xAB4, 'X'), + (0xAB5, 'V'), + (0xABA, 'X'), + (0xABC, 'V'), + (0xAC6, 'X'), + (0xAC7, 'V'), + (0xACA, 'X'), + (0xACB, 'V'), + (0xACE, 'X'), + (0xAD0, 'V'), + (0xAD1, 'X'), + (0xAE0, 'V'), + (0xAE4, 'X'), + (0xAE6, 'V'), + (0xAF2, 'X'), + (0xAF9, 'V'), + (0xB00, 'X'), + (0xB01, 'V'), + (0xB04, 'X'), + (0xB05, 'V'), + (0xB0D, 'X'), + (0xB0F, 'V'), + (0xB11, 'X'), + (0xB13, 'V'), + (0xB29, 'X'), + (0xB2A, 'V'), + (0xB31, 'X'), + (0xB32, 'V'), + (0xB34, 'X'), + (0xB35, 'V'), + (0xB3A, 'X'), + (0xB3C, 'V'), + (0xB45, 'X'), + (0xB47, 'V'), + (0xB49, 'X'), + (0xB4B, 'V'), + (0xB4E, 'X'), + (0xB56, 'V'), + (0xB58, 'X'), + (0xB5C, 'M', u'ଡ଼'), + (0xB5D, 'M', u'ଢ଼'), + (0xB5E, 'X'), + (0xB5F, 'V'), + (0xB64, 'X'), + (0xB66, 'V'), + (0xB78, 'X'), + (0xB82, 'V'), + (0xB84, 'X'), + (0xB85, 'V'), + (0xB8B, 'X'), + (0xB8E, 'V'), + (0xB91, 'X'), + (0xB92, 'V'), + (0xB96, 'X'), + (0xB99, 'V'), + (0xB9B, 'X'), + (0xB9C, 'V'), + (0xB9D, 'X'), + (0xB9E, 'V'), + (0xBA0, 'X'), + (0xBA3, 'V'), + (0xBA5, 'X'), + (0xBA8, 'V'), + (0xBAB, 'X'), + (0xBAE, 'V'), + (0xBBA, 'X'), + (0xBBE, 'V'), + (0xBC3, 'X'), + (0xBC6, 'V'), + (0xBC9, 'X'), + (0xBCA, 'V'), + (0xBCE, 'X'), + (0xBD0, 'V'), + (0xBD1, 'X'), + (0xBD7, 'V'), + (0xBD8, 'X'), + (0xBE6, 'V'), + (0xBFB, 'X'), + (0xC00, 'V'), + (0xC0D, 'X'), + (0xC0E, 'V'), + (0xC11, 'X'), + (0xC12, 'V'), + ] + +def _seg_12(): + return [ + (0xC29, 'X'), + (0xC2A, 'V'), + (0xC3A, 'X'), + (0xC3D, 'V'), + (0xC45, 'X'), + (0xC46, 'V'), + (0xC49, 'X'), + (0xC4A, 'V'), + (0xC4E, 'X'), + (0xC55, 'V'), + (0xC57, 'X'), + (0xC58, 'V'), + (0xC5B, 'X'), + (0xC60, 'V'), + (0xC64, 'X'), + (0xC66, 'V'), + (0xC70, 'X'), + (0xC78, 'V'), + (0xC8D, 'X'), + (0xC8E, 'V'), + (0xC91, 'X'), + (0xC92, 'V'), + (0xCA9, 'X'), + (0xCAA, 'V'), + (0xCB4, 'X'), + (0xCB5, 'V'), + (0xCBA, 'X'), + (0xCBC, 'V'), + (0xCC5, 'X'), + (0xCC6, 'V'), + (0xCC9, 'X'), + (0xCCA, 'V'), + (0xCCE, 'X'), + (0xCD5, 'V'), + (0xCD7, 'X'), + (0xCDE, 'V'), + (0xCDF, 'X'), + (0xCE0, 'V'), + (0xCE4, 'X'), + (0xCE6, 'V'), + (0xCF0, 'X'), + (0xCF1, 'V'), + (0xCF3, 'X'), + (0xD00, 'V'), + (0xD04, 'X'), + (0xD05, 'V'), + (0xD0D, 'X'), + (0xD0E, 'V'), + (0xD11, 'X'), + (0xD12, 'V'), + (0xD45, 'X'), + (0xD46, 'V'), + (0xD49, 'X'), + (0xD4A, 'V'), + (0xD50, 'X'), + (0xD54, 'V'), + (0xD64, 'X'), + (0xD66, 'V'), + (0xD80, 'X'), + (0xD82, 'V'), + (0xD84, 'X'), + (0xD85, 'V'), + (0xD97, 'X'), + (0xD9A, 'V'), + (0xDB2, 'X'), + (0xDB3, 'V'), + (0xDBC, 'X'), + (0xDBD, 'V'), + (0xDBE, 'X'), + (0xDC0, 'V'), + (0xDC7, 'X'), + (0xDCA, 'V'), + (0xDCB, 'X'), + (0xDCF, 'V'), + (0xDD5, 'X'), + (0xDD6, 'V'), + (0xDD7, 'X'), + (0xDD8, 'V'), + (0xDE0, 'X'), + (0xDE6, 'V'), + (0xDF0, 'X'), + (0xDF2, 'V'), + (0xDF5, 'X'), + (0xE01, 'V'), + (0xE33, 'M', u'ํา'), + (0xE34, 'V'), + (0xE3B, 'X'), + (0xE3F, 'V'), + (0xE5C, 'X'), + (0xE81, 'V'), + (0xE83, 'X'), + (0xE84, 'V'), + (0xE85, 'X'), + (0xE87, 'V'), + (0xE89, 'X'), + (0xE8A, 'V'), + (0xE8B, 'X'), + (0xE8D, 'V'), + (0xE8E, 'X'), + (0xE94, 'V'), + ] + +def _seg_13(): + return [ + (0xE98, 'X'), + (0xE99, 'V'), + (0xEA0, 'X'), + (0xEA1, 'V'), + (0xEA4, 'X'), + (0xEA5, 'V'), + (0xEA6, 'X'), + (0xEA7, 'V'), + (0xEA8, 'X'), + (0xEAA, 'V'), + (0xEAC, 'X'), + (0xEAD, 'V'), + (0xEB3, 'M', u'ໍາ'), + (0xEB4, 'V'), + (0xEBA, 'X'), + (0xEBB, 'V'), + (0xEBE, 'X'), + (0xEC0, 'V'), + (0xEC5, 'X'), + (0xEC6, 'V'), + (0xEC7, 'X'), + (0xEC8, 'V'), + (0xECE, 'X'), + (0xED0, 'V'), + (0xEDA, 'X'), + (0xEDC, 'M', u'ຫນ'), + (0xEDD, 'M', u'ຫມ'), + (0xEDE, 'V'), + (0xEE0, 'X'), + (0xF00, 'V'), + (0xF0C, 'M', u'་'), + (0xF0D, 'V'), + (0xF43, 'M', u'གྷ'), + (0xF44, 'V'), + (0xF48, 'X'), + (0xF49, 'V'), + (0xF4D, 'M', u'ཌྷ'), + (0xF4E, 'V'), + (0xF52, 'M', u'དྷ'), + (0xF53, 'V'), + (0xF57, 'M', u'བྷ'), + (0xF58, 'V'), + (0xF5C, 'M', u'ཛྷ'), + (0xF5D, 'V'), + (0xF69, 'M', u'ཀྵ'), + (0xF6A, 'V'), + (0xF6D, 'X'), + (0xF71, 'V'), + (0xF73, 'M', u'ཱི'), + (0xF74, 'V'), + (0xF75, 'M', u'ཱུ'), + (0xF76, 'M', u'ྲྀ'), + (0xF77, 'M', u'ྲཱྀ'), + (0xF78, 'M', u'ླྀ'), + (0xF79, 'M', u'ླཱྀ'), + (0xF7A, 'V'), + (0xF81, 'M', u'ཱྀ'), + (0xF82, 'V'), + (0xF93, 'M', u'ྒྷ'), + (0xF94, 'V'), + (0xF98, 'X'), + (0xF99, 'V'), + (0xF9D, 'M', u'ྜྷ'), + (0xF9E, 'V'), + (0xFA2, 'M', u'ྡྷ'), + (0xFA3, 'V'), + (0xFA7, 'M', u'ྦྷ'), + (0xFA8, 'V'), + (0xFAC, 'M', u'ྫྷ'), + (0xFAD, 'V'), + (0xFB9, 'M', u'ྐྵ'), + (0xFBA, 'V'), + (0xFBD, 'X'), + (0xFBE, 'V'), + (0xFCD, 'X'), + (0xFCE, 'V'), + (0xFDB, 'X'), + (0x1000, 'V'), + (0x10A0, 'X'), + (0x10C7, 'M', u'ⴧ'), + (0x10C8, 'X'), + (0x10CD, 'M', u'ⴭ'), + (0x10CE, 'X'), + (0x10D0, 'V'), + (0x10FC, 'M', u'ნ'), + (0x10FD, 'V'), + (0x115F, 'X'), + (0x1161, 'V'), + (0x1249, 'X'), + (0x124A, 'V'), + (0x124E, 'X'), + (0x1250, 'V'), + (0x1257, 'X'), + (0x1258, 'V'), + (0x1259, 'X'), + (0x125A, 'V'), + (0x125E, 'X'), + (0x1260, 'V'), + (0x1289, 'X'), + (0x128A, 'V'), + ] + +def _seg_14(): + return [ + (0x128E, 'X'), + (0x1290, 'V'), + (0x12B1, 'X'), + (0x12B2, 'V'), + (0x12B6, 'X'), + (0x12B8, 'V'), + (0x12BF, 'X'), + (0x12C0, 'V'), + (0x12C1, 'X'), + (0x12C2, 'V'), + (0x12C6, 'X'), + (0x12C8, 'V'), + (0x12D7, 'X'), + (0x12D8, 'V'), + (0x1311, 'X'), + (0x1312, 'V'), + (0x1316, 'X'), + (0x1318, 'V'), + (0x135B, 'X'), + (0x135D, 'V'), + (0x137D, 'X'), + (0x1380, 'V'), + (0x139A, 'X'), + (0x13A0, 'V'), + (0x13F6, 'X'), + (0x13F8, 'M', u'Ᏸ'), + (0x13F9, 'M', u'Ᏹ'), + (0x13FA, 'M', u'Ᏺ'), + (0x13FB, 'M', u'Ᏻ'), + (0x13FC, 'M', u'Ᏼ'), + (0x13FD, 'M', u'Ᏽ'), + (0x13FE, 'X'), + (0x1400, 'V'), + (0x1680, 'X'), + (0x1681, 'V'), + (0x169D, 'X'), + (0x16A0, 'V'), + (0x16F9, 'X'), + (0x1700, 'V'), + (0x170D, 'X'), + (0x170E, 'V'), + (0x1715, 'X'), + (0x1720, 'V'), + (0x1737, 'X'), + (0x1740, 'V'), + (0x1754, 'X'), + (0x1760, 'V'), + (0x176D, 'X'), + (0x176E, 'V'), + (0x1771, 'X'), + (0x1772, 'V'), + (0x1774, 'X'), + (0x1780, 'V'), + (0x17B4, 'X'), + (0x17B6, 'V'), + (0x17DE, 'X'), + (0x17E0, 'V'), + (0x17EA, 'X'), + (0x17F0, 'V'), + (0x17FA, 'X'), + (0x1800, 'V'), + (0x1806, 'X'), + (0x1807, 'V'), + (0x180B, 'I'), + (0x180E, 'X'), + (0x1810, 'V'), + (0x181A, 'X'), + (0x1820, 'V'), + (0x1879, 'X'), + (0x1880, 'V'), + (0x18AB, 'X'), + (0x18B0, 'V'), + (0x18F6, 'X'), + (0x1900, 'V'), + (0x191F, 'X'), + (0x1920, 'V'), + (0x192C, 'X'), + (0x1930, 'V'), + (0x193C, 'X'), + (0x1940, 'V'), + (0x1941, 'X'), + (0x1944, 'V'), + (0x196E, 'X'), + (0x1970, 'V'), + (0x1975, 'X'), + (0x1980, 'V'), + (0x19AC, 'X'), + (0x19B0, 'V'), + (0x19CA, 'X'), + (0x19D0, 'V'), + (0x19DB, 'X'), + (0x19DE, 'V'), + (0x1A1C, 'X'), + (0x1A1E, 'V'), + (0x1A5F, 'X'), + (0x1A60, 'V'), + (0x1A7D, 'X'), + (0x1A7F, 'V'), + (0x1A8A, 'X'), + (0x1A90, 'V'), + ] + +def _seg_15(): + return [ + (0x1A9A, 'X'), + (0x1AA0, 'V'), + (0x1AAE, 'X'), + (0x1AB0, 'V'), + (0x1ABF, 'X'), + (0x1B00, 'V'), + (0x1B4C, 'X'), + (0x1B50, 'V'), + (0x1B7D, 'X'), + (0x1B80, 'V'), + (0x1BF4, 'X'), + (0x1BFC, 'V'), + (0x1C38, 'X'), + (0x1C3B, 'V'), + (0x1C4A, 'X'), + (0x1C4D, 'V'), + (0x1C80, 'M', u'в'), + (0x1C81, 'M', u'д'), + (0x1C82, 'M', u'о'), + (0x1C83, 'M', u'с'), + (0x1C84, 'M', u'т'), + (0x1C86, 'M', u'ъ'), + (0x1C87, 'M', u'ѣ'), + (0x1C88, 'M', u'ꙋ'), + (0x1C89, 'X'), + (0x1CC0, 'V'), + (0x1CC8, 'X'), + (0x1CD0, 'V'), + (0x1CFA, 'X'), + (0x1D00, 'V'), + (0x1D2C, 'M', u'a'), + (0x1D2D, 'M', u'æ'), + (0x1D2E, 'M', u'b'), + (0x1D2F, 'V'), + (0x1D30, 'M', u'd'), + (0x1D31, 'M', u'e'), + (0x1D32, 'M', u'ǝ'), + (0x1D33, 'M', u'g'), + (0x1D34, 'M', u'h'), + (0x1D35, 'M', u'i'), + (0x1D36, 'M', u'j'), + (0x1D37, 'M', u'k'), + (0x1D38, 'M', u'l'), + (0x1D39, 'M', u'm'), + (0x1D3A, 'M', u'n'), + (0x1D3B, 'V'), + (0x1D3C, 'M', u'o'), + (0x1D3D, 'M', u'ȣ'), + (0x1D3E, 'M', u'p'), + (0x1D3F, 'M', u'r'), + (0x1D40, 'M', u't'), + (0x1D41, 'M', u'u'), + (0x1D42, 'M', u'w'), + (0x1D43, 'M', u'a'), + (0x1D44, 'M', u'ɐ'), + (0x1D45, 'M', u'ɑ'), + (0x1D46, 'M', u'ᴂ'), + (0x1D47, 'M', u'b'), + (0x1D48, 'M', u'd'), + (0x1D49, 'M', u'e'), + (0x1D4A, 'M', u'ə'), + (0x1D4B, 'M', u'ɛ'), + (0x1D4C, 'M', u'ɜ'), + (0x1D4D, 'M', u'g'), + (0x1D4E, 'V'), + (0x1D4F, 'M', u'k'), + (0x1D50, 'M', u'm'), + (0x1D51, 'M', u'ŋ'), + (0x1D52, 'M', u'o'), + (0x1D53, 'M', u'ɔ'), + (0x1D54, 'M', u'ᴖ'), + (0x1D55, 'M', u'ᴗ'), + (0x1D56, 'M', u'p'), + (0x1D57, 'M', u't'), + (0x1D58, 'M', u'u'), + (0x1D59, 'M', u'ᴝ'), + (0x1D5A, 'M', u'ɯ'), + (0x1D5B, 'M', u'v'), + (0x1D5C, 'M', u'ᴥ'), + (0x1D5D, 'M', u'β'), + (0x1D5E, 'M', u'γ'), + (0x1D5F, 'M', u'δ'), + (0x1D60, 'M', u'φ'), + (0x1D61, 'M', u'χ'), + (0x1D62, 'M', u'i'), + (0x1D63, 'M', u'r'), + (0x1D64, 'M', u'u'), + (0x1D65, 'M', u'v'), + (0x1D66, 'M', u'β'), + (0x1D67, 'M', u'γ'), + (0x1D68, 'M', u'ρ'), + (0x1D69, 'M', u'φ'), + (0x1D6A, 'M', u'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', u'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', u'ɒ'), + (0x1D9C, 'M', u'c'), + (0x1D9D, 'M', u'ɕ'), + (0x1D9E, 'M', u'ð'), + ] + +def _seg_16(): + return [ + (0x1D9F, 'M', u'ɜ'), + (0x1DA0, 'M', u'f'), + (0x1DA1, 'M', u'ɟ'), + (0x1DA2, 'M', u'ɡ'), + (0x1DA3, 'M', u'ɥ'), + (0x1DA4, 'M', u'ɨ'), + (0x1DA5, 'M', u'ɩ'), + (0x1DA6, 'M', u'ɪ'), + (0x1DA7, 'M', u'ᵻ'), + (0x1DA8, 'M', u'ʝ'), + (0x1DA9, 'M', u'ɭ'), + (0x1DAA, 'M', u'ᶅ'), + (0x1DAB, 'M', u'ʟ'), + (0x1DAC, 'M', u'ɱ'), + (0x1DAD, 'M', u'ɰ'), + (0x1DAE, 'M', u'ɲ'), + (0x1DAF, 'M', u'ɳ'), + (0x1DB0, 'M', u'ɴ'), + (0x1DB1, 'M', u'ɵ'), + (0x1DB2, 'M', u'ɸ'), + (0x1DB3, 'M', u'ʂ'), + (0x1DB4, 'M', u'ʃ'), + (0x1DB5, 'M', u'ƫ'), + (0x1DB6, 'M', u'ʉ'), + (0x1DB7, 'M', u'ʊ'), + (0x1DB8, 'M', u'ᴜ'), + (0x1DB9, 'M', u'ʋ'), + (0x1DBA, 'M', u'ʌ'), + (0x1DBB, 'M', u'z'), + (0x1DBC, 'M', u'ʐ'), + (0x1DBD, 'M', u'ʑ'), + (0x1DBE, 'M', u'ʒ'), + (0x1DBF, 'M', u'θ'), + (0x1DC0, 'V'), + (0x1DFA, 'X'), + (0x1DFB, 'V'), + (0x1E00, 'M', u'ḁ'), + (0x1E01, 'V'), + (0x1E02, 'M', u'ḃ'), + (0x1E03, 'V'), + (0x1E04, 'M', u'ḅ'), + (0x1E05, 'V'), + (0x1E06, 'M', u'ḇ'), + (0x1E07, 'V'), + (0x1E08, 'M', u'ḉ'), + (0x1E09, 'V'), + (0x1E0A, 'M', u'ḋ'), + (0x1E0B, 'V'), + (0x1E0C, 'M', u'ḍ'), + (0x1E0D, 'V'), + (0x1E0E, 'M', u'ḏ'), + (0x1E0F, 'V'), + (0x1E10, 'M', u'ḑ'), + (0x1E11, 'V'), + (0x1E12, 'M', u'ḓ'), + (0x1E13, 'V'), + (0x1E14, 'M', u'ḕ'), + (0x1E15, 'V'), + (0x1E16, 'M', u'ḗ'), + (0x1E17, 'V'), + (0x1E18, 'M', u'ḙ'), + (0x1E19, 'V'), + (0x1E1A, 'M', u'ḛ'), + (0x1E1B, 'V'), + (0x1E1C, 'M', u'ḝ'), + (0x1E1D, 'V'), + (0x1E1E, 'M', u'ḟ'), + (0x1E1F, 'V'), + (0x1E20, 'M', u'ḡ'), + (0x1E21, 'V'), + (0x1E22, 'M', u'ḣ'), + (0x1E23, 'V'), + (0x1E24, 'M', u'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', u'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', u'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', u'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', u'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', u'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', u'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', u'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', u'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', u'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', u'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', u'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', u'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', u'ḿ'), + (0x1E3F, 'V'), + ] + +def _seg_17(): + return [ + (0x1E40, 'M', u'ṁ'), + (0x1E41, 'V'), + (0x1E42, 'M', u'ṃ'), + (0x1E43, 'V'), + (0x1E44, 'M', u'ṅ'), + (0x1E45, 'V'), + (0x1E46, 'M', u'ṇ'), + (0x1E47, 'V'), + (0x1E48, 'M', u'ṉ'), + (0x1E49, 'V'), + (0x1E4A, 'M', u'ṋ'), + (0x1E4B, 'V'), + (0x1E4C, 'M', u'ṍ'), + (0x1E4D, 'V'), + (0x1E4E, 'M', u'ṏ'), + (0x1E4F, 'V'), + (0x1E50, 'M', u'ṑ'), + (0x1E51, 'V'), + (0x1E52, 'M', u'ṓ'), + (0x1E53, 'V'), + (0x1E54, 'M', u'ṕ'), + (0x1E55, 'V'), + (0x1E56, 'M', u'ṗ'), + (0x1E57, 'V'), + (0x1E58, 'M', u'ṙ'), + (0x1E59, 'V'), + (0x1E5A, 'M', u'ṛ'), + (0x1E5B, 'V'), + (0x1E5C, 'M', u'ṝ'), + (0x1E5D, 'V'), + (0x1E5E, 'M', u'ṟ'), + (0x1E5F, 'V'), + (0x1E60, 'M', u'ṡ'), + (0x1E61, 'V'), + (0x1E62, 'M', u'ṣ'), + (0x1E63, 'V'), + (0x1E64, 'M', u'ṥ'), + (0x1E65, 'V'), + (0x1E66, 'M', u'ṧ'), + (0x1E67, 'V'), + (0x1E68, 'M', u'ṩ'), + (0x1E69, 'V'), + (0x1E6A, 'M', u'ṫ'), + (0x1E6B, 'V'), + (0x1E6C, 'M', u'ṭ'), + (0x1E6D, 'V'), + (0x1E6E, 'M', u'ṯ'), + (0x1E6F, 'V'), + (0x1E70, 'M', u'ṱ'), + (0x1E71, 'V'), + (0x1E72, 'M', u'ṳ'), + (0x1E73, 'V'), + (0x1E74, 'M', u'ṵ'), + (0x1E75, 'V'), + (0x1E76, 'M', u'ṷ'), + (0x1E77, 'V'), + (0x1E78, 'M', u'ṹ'), + (0x1E79, 'V'), + (0x1E7A, 'M', u'ṻ'), + (0x1E7B, 'V'), + (0x1E7C, 'M', u'ṽ'), + (0x1E7D, 'V'), + (0x1E7E, 'M', u'ṿ'), + (0x1E7F, 'V'), + (0x1E80, 'M', u'ẁ'), + (0x1E81, 'V'), + (0x1E82, 'M', u'ẃ'), + (0x1E83, 'V'), + (0x1E84, 'M', u'ẅ'), + (0x1E85, 'V'), + (0x1E86, 'M', u'ẇ'), + (0x1E87, 'V'), + (0x1E88, 'M', u'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', u'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', u'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', u'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', u'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', u'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', u'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', u'aʾ'), + (0x1E9B, 'M', u'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', u'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', u'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', u'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', u'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', u'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', u'ẩ'), + ] + +def _seg_18(): + return [ + (0x1EA9, 'V'), + (0x1EAA, 'M', u'ẫ'), + (0x1EAB, 'V'), + (0x1EAC, 'M', u'ậ'), + (0x1EAD, 'V'), + (0x1EAE, 'M', u'ắ'), + (0x1EAF, 'V'), + (0x1EB0, 'M', u'ằ'), + (0x1EB1, 'V'), + (0x1EB2, 'M', u'ẳ'), + (0x1EB3, 'V'), + (0x1EB4, 'M', u'ẵ'), + (0x1EB5, 'V'), + (0x1EB6, 'M', u'ặ'), + (0x1EB7, 'V'), + (0x1EB8, 'M', u'ẹ'), + (0x1EB9, 'V'), + (0x1EBA, 'M', u'ẻ'), + (0x1EBB, 'V'), + (0x1EBC, 'M', u'ẽ'), + (0x1EBD, 'V'), + (0x1EBE, 'M', u'ế'), + (0x1EBF, 'V'), + (0x1EC0, 'M', u'ề'), + (0x1EC1, 'V'), + (0x1EC2, 'M', u'ể'), + (0x1EC3, 'V'), + (0x1EC4, 'M', u'ễ'), + (0x1EC5, 'V'), + (0x1EC6, 'M', u'ệ'), + (0x1EC7, 'V'), + (0x1EC8, 'M', u'ỉ'), + (0x1EC9, 'V'), + (0x1ECA, 'M', u'ị'), + (0x1ECB, 'V'), + (0x1ECC, 'M', u'ọ'), + (0x1ECD, 'V'), + (0x1ECE, 'M', u'ỏ'), + (0x1ECF, 'V'), + (0x1ED0, 'M', u'ố'), + (0x1ED1, 'V'), + (0x1ED2, 'M', u'ồ'), + (0x1ED3, 'V'), + (0x1ED4, 'M', u'ổ'), + (0x1ED5, 'V'), + (0x1ED6, 'M', u'ỗ'), + (0x1ED7, 'V'), + (0x1ED8, 'M', u'ộ'), + (0x1ED9, 'V'), + (0x1EDA, 'M', u'ớ'), + (0x1EDB, 'V'), + (0x1EDC, 'M', u'ờ'), + (0x1EDD, 'V'), + (0x1EDE, 'M', u'ở'), + (0x1EDF, 'V'), + (0x1EE0, 'M', u'ỡ'), + (0x1EE1, 'V'), + (0x1EE2, 'M', u'ợ'), + (0x1EE3, 'V'), + (0x1EE4, 'M', u'ụ'), + (0x1EE5, 'V'), + (0x1EE6, 'M', u'ủ'), + (0x1EE7, 'V'), + (0x1EE8, 'M', u'ứ'), + (0x1EE9, 'V'), + (0x1EEA, 'M', u'ừ'), + (0x1EEB, 'V'), + (0x1EEC, 'M', u'ử'), + (0x1EED, 'V'), + (0x1EEE, 'M', u'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', u'ự'), + (0x1EF1, 'V'), + (0x1EF2, 'M', u'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', u'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', u'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', u'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', u'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', u'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', u'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', u'ἀ'), + (0x1F09, 'M', u'ἁ'), + (0x1F0A, 'M', u'ἂ'), + (0x1F0B, 'M', u'ἃ'), + (0x1F0C, 'M', u'ἄ'), + (0x1F0D, 'M', u'ἅ'), + (0x1F0E, 'M', u'ἆ'), + (0x1F0F, 'M', u'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', u'ἐ'), + (0x1F19, 'M', u'ἑ'), + (0x1F1A, 'M', u'ἒ'), + ] + +def _seg_19(): + return [ + (0x1F1B, 'M', u'ἓ'), + (0x1F1C, 'M', u'ἔ'), + (0x1F1D, 'M', u'ἕ'), + (0x1F1E, 'X'), + (0x1F20, 'V'), + (0x1F28, 'M', u'ἠ'), + (0x1F29, 'M', u'ἡ'), + (0x1F2A, 'M', u'ἢ'), + (0x1F2B, 'M', u'ἣ'), + (0x1F2C, 'M', u'ἤ'), + (0x1F2D, 'M', u'ἥ'), + (0x1F2E, 'M', u'ἦ'), + (0x1F2F, 'M', u'ἧ'), + (0x1F30, 'V'), + (0x1F38, 'M', u'ἰ'), + (0x1F39, 'M', u'ἱ'), + (0x1F3A, 'M', u'ἲ'), + (0x1F3B, 'M', u'ἳ'), + (0x1F3C, 'M', u'ἴ'), + (0x1F3D, 'M', u'ἵ'), + (0x1F3E, 'M', u'ἶ'), + (0x1F3F, 'M', u'ἷ'), + (0x1F40, 'V'), + (0x1F46, 'X'), + (0x1F48, 'M', u'ὀ'), + (0x1F49, 'M', u'ὁ'), + (0x1F4A, 'M', u'ὂ'), + (0x1F4B, 'M', u'ὃ'), + (0x1F4C, 'M', u'ὄ'), + (0x1F4D, 'M', u'ὅ'), + (0x1F4E, 'X'), + (0x1F50, 'V'), + (0x1F58, 'X'), + (0x1F59, 'M', u'ὑ'), + (0x1F5A, 'X'), + (0x1F5B, 'M', u'ὓ'), + (0x1F5C, 'X'), + (0x1F5D, 'M', u'ὕ'), + (0x1F5E, 'X'), + (0x1F5F, 'M', u'ὗ'), + (0x1F60, 'V'), + (0x1F68, 'M', u'ὠ'), + (0x1F69, 'M', u'ὡ'), + (0x1F6A, 'M', u'ὢ'), + (0x1F6B, 'M', u'ὣ'), + (0x1F6C, 'M', u'ὤ'), + (0x1F6D, 'M', u'ὥ'), + (0x1F6E, 'M', u'ὦ'), + (0x1F6F, 'M', u'ὧ'), + (0x1F70, 'V'), + (0x1F71, 'M', u'ά'), + (0x1F72, 'V'), + (0x1F73, 'M', u'έ'), + (0x1F74, 'V'), + (0x1F75, 'M', u'ή'), + (0x1F76, 'V'), + (0x1F77, 'M', u'ί'), + (0x1F78, 'V'), + (0x1F79, 'M', u'ό'), + (0x1F7A, 'V'), + (0x1F7B, 'M', u'ύ'), + (0x1F7C, 'V'), + (0x1F7D, 'M', u'ώ'), + (0x1F7E, 'X'), + (0x1F80, 'M', u'ἀι'), + (0x1F81, 'M', u'ἁι'), + (0x1F82, 'M', u'ἂι'), + (0x1F83, 'M', u'ἃι'), + (0x1F84, 'M', u'ἄι'), + (0x1F85, 'M', u'ἅι'), + (0x1F86, 'M', u'ἆι'), + (0x1F87, 'M', u'ἇι'), + (0x1F88, 'M', u'ἀι'), + (0x1F89, 'M', u'ἁι'), + (0x1F8A, 'M', u'ἂι'), + (0x1F8B, 'M', u'ἃι'), + (0x1F8C, 'M', u'ἄι'), + (0x1F8D, 'M', u'ἅι'), + (0x1F8E, 'M', u'ἆι'), + (0x1F8F, 'M', u'ἇι'), + (0x1F90, 'M', u'ἠι'), + (0x1F91, 'M', u'ἡι'), + (0x1F92, 'M', u'ἢι'), + (0x1F93, 'M', u'ἣι'), + (0x1F94, 'M', u'ἤι'), + (0x1F95, 'M', u'ἥι'), + (0x1F96, 'M', u'ἦι'), + (0x1F97, 'M', u'ἧι'), + (0x1F98, 'M', u'ἠι'), + (0x1F99, 'M', u'ἡι'), + (0x1F9A, 'M', u'ἢι'), + (0x1F9B, 'M', u'ἣι'), + (0x1F9C, 'M', u'ἤι'), + (0x1F9D, 'M', u'ἥι'), + (0x1F9E, 'M', u'ἦι'), + (0x1F9F, 'M', u'ἧι'), + (0x1FA0, 'M', u'ὠι'), + (0x1FA1, 'M', u'ὡι'), + (0x1FA2, 'M', u'ὢι'), + (0x1FA3, 'M', u'ὣι'), + ] + +def _seg_20(): + return [ + (0x1FA4, 'M', u'ὤι'), + (0x1FA5, 'M', u'ὥι'), + (0x1FA6, 'M', u'ὦι'), + (0x1FA7, 'M', u'ὧι'), + (0x1FA8, 'M', u'ὠι'), + (0x1FA9, 'M', u'ὡι'), + (0x1FAA, 'M', u'ὢι'), + (0x1FAB, 'M', u'ὣι'), + (0x1FAC, 'M', u'ὤι'), + (0x1FAD, 'M', u'ὥι'), + (0x1FAE, 'M', u'ὦι'), + (0x1FAF, 'M', u'ὧι'), + (0x1FB0, 'V'), + (0x1FB2, 'M', u'ὰι'), + (0x1FB3, 'M', u'αι'), + (0x1FB4, 'M', u'άι'), + (0x1FB5, 'X'), + (0x1FB6, 'V'), + (0x1FB7, 'M', u'ᾶι'), + (0x1FB8, 'M', u'ᾰ'), + (0x1FB9, 'M', u'ᾱ'), + (0x1FBA, 'M', u'ὰ'), + (0x1FBB, 'M', u'ά'), + (0x1FBC, 'M', u'αι'), + (0x1FBD, '3', u' ̓'), + (0x1FBE, 'M', u'ι'), + (0x1FBF, '3', u' ̓'), + (0x1FC0, '3', u' ͂'), + (0x1FC1, '3', u' ̈͂'), + (0x1FC2, 'M', u'ὴι'), + (0x1FC3, 'M', u'ηι'), + (0x1FC4, 'M', u'ήι'), + (0x1FC5, 'X'), + (0x1FC6, 'V'), + (0x1FC7, 'M', u'ῆι'), + (0x1FC8, 'M', u'ὲ'), + (0x1FC9, 'M', u'έ'), + (0x1FCA, 'M', u'ὴ'), + (0x1FCB, 'M', u'ή'), + (0x1FCC, 'M', u'ηι'), + (0x1FCD, '3', u' ̓̀'), + (0x1FCE, '3', u' ̓́'), + (0x1FCF, '3', u' ̓͂'), + (0x1FD0, 'V'), + (0x1FD3, 'M', u'ΐ'), + (0x1FD4, 'X'), + (0x1FD6, 'V'), + (0x1FD8, 'M', u'ῐ'), + (0x1FD9, 'M', u'ῑ'), + (0x1FDA, 'M', u'ὶ'), + (0x1FDB, 'M', u'ί'), + (0x1FDC, 'X'), + (0x1FDD, '3', u' ̔̀'), + (0x1FDE, '3', u' ̔́'), + (0x1FDF, '3', u' ̔͂'), + (0x1FE0, 'V'), + (0x1FE3, 'M', u'ΰ'), + (0x1FE4, 'V'), + (0x1FE8, 'M', u'ῠ'), + (0x1FE9, 'M', u'ῡ'), + (0x1FEA, 'M', u'ὺ'), + (0x1FEB, 'M', u'ύ'), + (0x1FEC, 'M', u'ῥ'), + (0x1FED, '3', u' ̈̀'), + (0x1FEE, '3', u' ̈́'), + (0x1FEF, '3', u'`'), + (0x1FF0, 'X'), + (0x1FF2, 'M', u'ὼι'), + (0x1FF3, 'M', u'ωι'), + (0x1FF4, 'M', u'ώι'), + (0x1FF5, 'X'), + (0x1FF6, 'V'), + (0x1FF7, 'M', u'ῶι'), + (0x1FF8, 'M', u'ὸ'), + (0x1FF9, 'M', u'ό'), + (0x1FFA, 'M', u'ὼ'), + (0x1FFB, 'M', u'ώ'), + (0x1FFC, 'M', u'ωι'), + (0x1FFD, '3', u' ́'), + (0x1FFE, '3', u' ̔'), + (0x1FFF, 'X'), + (0x2000, '3', u' '), + (0x200B, 'I'), + (0x200C, 'D', u''), + (0x200E, 'X'), + (0x2010, 'V'), + (0x2011, 'M', u'‐'), + (0x2012, 'V'), + (0x2017, '3', u' ̳'), + (0x2018, 'V'), + (0x2024, 'X'), + (0x2027, 'V'), + (0x2028, 'X'), + (0x202F, '3', u' '), + (0x2030, 'V'), + (0x2033, 'M', u'′′'), + (0x2034, 'M', u'′′′'), + (0x2035, 'V'), + (0x2036, 'M', u'‵‵'), + (0x2037, 'M', u'‵‵‵'), + ] + +def _seg_21(): + return [ + (0x2038, 'V'), + (0x203C, '3', u'!!'), + (0x203D, 'V'), + (0x203E, '3', u' ̅'), + (0x203F, 'V'), + (0x2047, '3', u'??'), + (0x2048, '3', u'?!'), + (0x2049, '3', u'!?'), + (0x204A, 'V'), + (0x2057, 'M', u'′′′′'), + (0x2058, 'V'), + (0x205F, '3', u' '), + (0x2060, 'I'), + (0x2061, 'X'), + (0x2064, 'I'), + (0x2065, 'X'), + (0x2070, 'M', u'0'), + (0x2071, 'M', u'i'), + (0x2072, 'X'), + (0x2074, 'M', u'4'), + (0x2075, 'M', u'5'), + (0x2076, 'M', u'6'), + (0x2077, 'M', u'7'), + (0x2078, 'M', u'8'), + (0x2079, 'M', u'9'), + (0x207A, '3', u'+'), + (0x207B, 'M', u'−'), + (0x207C, '3', u'='), + (0x207D, '3', u'('), + (0x207E, '3', u')'), + (0x207F, 'M', u'n'), + (0x2080, 'M', u'0'), + (0x2081, 'M', u'1'), + (0x2082, 'M', u'2'), + (0x2083, 'M', u'3'), + (0x2084, 'M', u'4'), + (0x2085, 'M', u'5'), + (0x2086, 'M', u'6'), + (0x2087, 'M', u'7'), + (0x2088, 'M', u'8'), + (0x2089, 'M', u'9'), + (0x208A, '3', u'+'), + (0x208B, 'M', u'−'), + (0x208C, '3', u'='), + (0x208D, '3', u'('), + (0x208E, '3', u')'), + (0x208F, 'X'), + (0x2090, 'M', u'a'), + (0x2091, 'M', u'e'), + (0x2092, 'M', u'o'), + (0x2093, 'M', u'x'), + (0x2094, 'M', u'ə'), + (0x2095, 'M', u'h'), + (0x2096, 'M', u'k'), + (0x2097, 'M', u'l'), + (0x2098, 'M', u'm'), + (0x2099, 'M', u'n'), + (0x209A, 'M', u'p'), + (0x209B, 'M', u's'), + (0x209C, 'M', u't'), + (0x209D, 'X'), + (0x20A0, 'V'), + (0x20A8, 'M', u'rs'), + (0x20A9, 'V'), + (0x20C0, 'X'), + (0x20D0, 'V'), + (0x20F1, 'X'), + (0x2100, '3', u'a/c'), + (0x2101, '3', u'a/s'), + (0x2102, 'M', u'c'), + (0x2103, 'M', u'°c'), + (0x2104, 'V'), + (0x2105, '3', u'c/o'), + (0x2106, '3', u'c/u'), + (0x2107, 'M', u'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', u'°f'), + (0x210A, 'M', u'g'), + (0x210B, 'M', u'h'), + (0x210F, 'M', u'ħ'), + (0x2110, 'M', u'i'), + (0x2112, 'M', u'l'), + (0x2114, 'V'), + (0x2115, 'M', u'n'), + (0x2116, 'M', u'no'), + (0x2117, 'V'), + (0x2119, 'M', u'p'), + (0x211A, 'M', u'q'), + (0x211B, 'M', u'r'), + (0x211E, 'V'), + (0x2120, 'M', u'sm'), + (0x2121, 'M', u'tel'), + (0x2122, 'M', u'tm'), + (0x2123, 'V'), + (0x2124, 'M', u'z'), + (0x2125, 'V'), + (0x2126, 'M', u'ω'), + (0x2127, 'V'), + (0x2128, 'M', u'z'), + (0x2129, 'V'), + ] + +def _seg_22(): + return [ + (0x212A, 'M', u'k'), + (0x212B, 'M', u'å'), + (0x212C, 'M', u'b'), + (0x212D, 'M', u'c'), + (0x212E, 'V'), + (0x212F, 'M', u'e'), + (0x2131, 'M', u'f'), + (0x2132, 'X'), + (0x2133, 'M', u'm'), + (0x2134, 'M', u'o'), + (0x2135, 'M', u'א'), + (0x2136, 'M', u'ב'), + (0x2137, 'M', u'ג'), + (0x2138, 'M', u'ד'), + (0x2139, 'M', u'i'), + (0x213A, 'V'), + (0x213B, 'M', u'fax'), + (0x213C, 'M', u'π'), + (0x213D, 'M', u'γ'), + (0x213F, 'M', u'π'), + (0x2140, 'M', u'∑'), + (0x2141, 'V'), + (0x2145, 'M', u'd'), + (0x2147, 'M', u'e'), + (0x2148, 'M', u'i'), + (0x2149, 'M', u'j'), + (0x214A, 'V'), + (0x2150, 'M', u'1⁄7'), + (0x2151, 'M', u'1⁄9'), + (0x2152, 'M', u'1⁄10'), + (0x2153, 'M', u'1⁄3'), + (0x2154, 'M', u'2⁄3'), + (0x2155, 'M', u'1⁄5'), + (0x2156, 'M', u'2⁄5'), + (0x2157, 'M', u'3⁄5'), + (0x2158, 'M', u'4⁄5'), + (0x2159, 'M', u'1⁄6'), + (0x215A, 'M', u'5⁄6'), + (0x215B, 'M', u'1⁄8'), + (0x215C, 'M', u'3⁄8'), + (0x215D, 'M', u'5⁄8'), + (0x215E, 'M', u'7⁄8'), + (0x215F, 'M', u'1⁄'), + (0x2160, 'M', u'i'), + (0x2161, 'M', u'ii'), + (0x2162, 'M', u'iii'), + (0x2163, 'M', u'iv'), + (0x2164, 'M', u'v'), + (0x2165, 'M', u'vi'), + (0x2166, 'M', u'vii'), + (0x2167, 'M', u'viii'), + (0x2168, 'M', u'ix'), + (0x2169, 'M', u'x'), + (0x216A, 'M', u'xi'), + (0x216B, 'M', u'xii'), + (0x216C, 'M', u'l'), + (0x216D, 'M', u'c'), + (0x216E, 'M', u'd'), + (0x216F, 'M', u'm'), + (0x2170, 'M', u'i'), + (0x2171, 'M', u'ii'), + (0x2172, 'M', u'iii'), + (0x2173, 'M', u'iv'), + (0x2174, 'M', u'v'), + (0x2175, 'M', u'vi'), + (0x2176, 'M', u'vii'), + (0x2177, 'M', u'viii'), + (0x2178, 'M', u'ix'), + (0x2179, 'M', u'x'), + (0x217A, 'M', u'xi'), + (0x217B, 'M', u'xii'), + (0x217C, 'M', u'l'), + (0x217D, 'M', u'c'), + (0x217E, 'M', u'd'), + (0x217F, 'M', u'm'), + (0x2180, 'V'), + (0x2183, 'X'), + (0x2184, 'V'), + (0x2189, 'M', u'0⁄3'), + (0x218A, 'V'), + (0x218C, 'X'), + (0x2190, 'V'), + (0x222C, 'M', u'∫∫'), + (0x222D, 'M', u'∫∫∫'), + (0x222E, 'V'), + (0x222F, 'M', u'∮∮'), + (0x2230, 'M', u'∮∮∮'), + (0x2231, 'V'), + (0x2260, '3'), + (0x2261, 'V'), + (0x226E, '3'), + (0x2270, 'V'), + (0x2329, 'M', u'〈'), + (0x232A, 'M', u'〉'), + (0x232B, 'V'), + (0x2427, 'X'), + (0x2440, 'V'), + (0x244B, 'X'), + (0x2460, 'M', u'1'), + (0x2461, 'M', u'2'), + ] + +def _seg_23(): + return [ + (0x2462, 'M', u'3'), + (0x2463, 'M', u'4'), + (0x2464, 'M', u'5'), + (0x2465, 'M', u'6'), + (0x2466, 'M', u'7'), + (0x2467, 'M', u'8'), + (0x2468, 'M', u'9'), + (0x2469, 'M', u'10'), + (0x246A, 'M', u'11'), + (0x246B, 'M', u'12'), + (0x246C, 'M', u'13'), + (0x246D, 'M', u'14'), + (0x246E, 'M', u'15'), + (0x246F, 'M', u'16'), + (0x2470, 'M', u'17'), + (0x2471, 'M', u'18'), + (0x2472, 'M', u'19'), + (0x2473, 'M', u'20'), + (0x2474, '3', u'(1)'), + (0x2475, '3', u'(2)'), + (0x2476, '3', u'(3)'), + (0x2477, '3', u'(4)'), + (0x2478, '3', u'(5)'), + (0x2479, '3', u'(6)'), + (0x247A, '3', u'(7)'), + (0x247B, '3', u'(8)'), + (0x247C, '3', u'(9)'), + (0x247D, '3', u'(10)'), + (0x247E, '3', u'(11)'), + (0x247F, '3', u'(12)'), + (0x2480, '3', u'(13)'), + (0x2481, '3', u'(14)'), + (0x2482, '3', u'(15)'), + (0x2483, '3', u'(16)'), + (0x2484, '3', u'(17)'), + (0x2485, '3', u'(18)'), + (0x2486, '3', u'(19)'), + (0x2487, '3', u'(20)'), + (0x2488, 'X'), + (0x249C, '3', u'(a)'), + (0x249D, '3', u'(b)'), + (0x249E, '3', u'(c)'), + (0x249F, '3', u'(d)'), + (0x24A0, '3', u'(e)'), + (0x24A1, '3', u'(f)'), + (0x24A2, '3', u'(g)'), + (0x24A3, '3', u'(h)'), + (0x24A4, '3', u'(i)'), + (0x24A5, '3', u'(j)'), + (0x24A6, '3', u'(k)'), + (0x24A7, '3', u'(l)'), + (0x24A8, '3', u'(m)'), + (0x24A9, '3', u'(n)'), + (0x24AA, '3', u'(o)'), + (0x24AB, '3', u'(p)'), + (0x24AC, '3', u'(q)'), + (0x24AD, '3', u'(r)'), + (0x24AE, '3', u'(s)'), + (0x24AF, '3', u'(t)'), + (0x24B0, '3', u'(u)'), + (0x24B1, '3', u'(v)'), + (0x24B2, '3', u'(w)'), + (0x24B3, '3', u'(x)'), + (0x24B4, '3', u'(y)'), + (0x24B5, '3', u'(z)'), + (0x24B6, 'M', u'a'), + (0x24B7, 'M', u'b'), + (0x24B8, 'M', u'c'), + (0x24B9, 'M', u'd'), + (0x24BA, 'M', u'e'), + (0x24BB, 'M', u'f'), + (0x24BC, 'M', u'g'), + (0x24BD, 'M', u'h'), + (0x24BE, 'M', u'i'), + (0x24BF, 'M', u'j'), + (0x24C0, 'M', u'k'), + (0x24C1, 'M', u'l'), + (0x24C2, 'M', u'm'), + (0x24C3, 'M', u'n'), + (0x24C4, 'M', u'o'), + (0x24C5, 'M', u'p'), + (0x24C6, 'M', u'q'), + (0x24C7, 'M', u'r'), + (0x24C8, 'M', u's'), + (0x24C9, 'M', u't'), + (0x24CA, 'M', u'u'), + (0x24CB, 'M', u'v'), + (0x24CC, 'M', u'w'), + (0x24CD, 'M', u'x'), + (0x24CE, 'M', u'y'), + (0x24CF, 'M', u'z'), + (0x24D0, 'M', u'a'), + (0x24D1, 'M', u'b'), + (0x24D2, 'M', u'c'), + (0x24D3, 'M', u'd'), + (0x24D4, 'M', u'e'), + (0x24D5, 'M', u'f'), + (0x24D6, 'M', u'g'), + (0x24D7, 'M', u'h'), + (0x24D8, 'M', u'i'), + ] + +def _seg_24(): + return [ + (0x24D9, 'M', u'j'), + (0x24DA, 'M', u'k'), + (0x24DB, 'M', u'l'), + (0x24DC, 'M', u'm'), + (0x24DD, 'M', u'n'), + (0x24DE, 'M', u'o'), + (0x24DF, 'M', u'p'), + (0x24E0, 'M', u'q'), + (0x24E1, 'M', u'r'), + (0x24E2, 'M', u's'), + (0x24E3, 'M', u't'), + (0x24E4, 'M', u'u'), + (0x24E5, 'M', u'v'), + (0x24E6, 'M', u'w'), + (0x24E7, 'M', u'x'), + (0x24E8, 'M', u'y'), + (0x24E9, 'M', u'z'), + (0x24EA, 'M', u'0'), + (0x24EB, 'V'), + (0x2A0C, 'M', u'∫∫∫∫'), + (0x2A0D, 'V'), + (0x2A74, '3', u'::='), + (0x2A75, '3', u'=='), + (0x2A76, '3', u'==='), + (0x2A77, 'V'), + (0x2ADC, 'M', u'⫝̸'), + (0x2ADD, 'V'), + (0x2B74, 'X'), + (0x2B76, 'V'), + (0x2B96, 'X'), + (0x2B98, 'V'), + (0x2BC9, 'X'), + (0x2BCA, 'V'), + (0x2BFF, 'X'), + (0x2C00, 'M', u'ⰰ'), + (0x2C01, 'M', u'ⰱ'), + (0x2C02, 'M', u'ⰲ'), + (0x2C03, 'M', u'ⰳ'), + (0x2C04, 'M', u'ⰴ'), + (0x2C05, 'M', u'ⰵ'), + (0x2C06, 'M', u'ⰶ'), + (0x2C07, 'M', u'ⰷ'), + (0x2C08, 'M', u'ⰸ'), + (0x2C09, 'M', u'ⰹ'), + (0x2C0A, 'M', u'ⰺ'), + (0x2C0B, 'M', u'ⰻ'), + (0x2C0C, 'M', u'ⰼ'), + (0x2C0D, 'M', u'ⰽ'), + (0x2C0E, 'M', u'ⰾ'), + (0x2C0F, 'M', u'ⰿ'), + (0x2C10, 'M', u'ⱀ'), + (0x2C11, 'M', u'ⱁ'), + (0x2C12, 'M', u'ⱂ'), + (0x2C13, 'M', u'ⱃ'), + (0x2C14, 'M', u'ⱄ'), + (0x2C15, 'M', u'ⱅ'), + (0x2C16, 'M', u'ⱆ'), + (0x2C17, 'M', u'ⱇ'), + (0x2C18, 'M', u'ⱈ'), + (0x2C19, 'M', u'ⱉ'), + (0x2C1A, 'M', u'ⱊ'), + (0x2C1B, 'M', u'ⱋ'), + (0x2C1C, 'M', u'ⱌ'), + (0x2C1D, 'M', u'ⱍ'), + (0x2C1E, 'M', u'ⱎ'), + (0x2C1F, 'M', u'ⱏ'), + (0x2C20, 'M', u'ⱐ'), + (0x2C21, 'M', u'ⱑ'), + (0x2C22, 'M', u'ⱒ'), + (0x2C23, 'M', u'ⱓ'), + (0x2C24, 'M', u'ⱔ'), + (0x2C25, 'M', u'ⱕ'), + (0x2C26, 'M', u'ⱖ'), + (0x2C27, 'M', u'ⱗ'), + (0x2C28, 'M', u'ⱘ'), + (0x2C29, 'M', u'ⱙ'), + (0x2C2A, 'M', u'ⱚ'), + (0x2C2B, 'M', u'ⱛ'), + (0x2C2C, 'M', u'ⱜ'), + (0x2C2D, 'M', u'ⱝ'), + (0x2C2E, 'M', u'ⱞ'), + (0x2C2F, 'X'), + (0x2C30, 'V'), + (0x2C5F, 'X'), + (0x2C60, 'M', u'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', u'ɫ'), + (0x2C63, 'M', u'ᵽ'), + (0x2C64, 'M', u'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', u'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', u'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', u'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', u'ɑ'), + (0x2C6E, 'M', u'ɱ'), + (0x2C6F, 'M', u'ɐ'), + (0x2C70, 'M', u'ɒ'), + ] + +def _seg_25(): + return [ + (0x2C71, 'V'), + (0x2C72, 'M', u'ⱳ'), + (0x2C73, 'V'), + (0x2C75, 'M', u'ⱶ'), + (0x2C76, 'V'), + (0x2C7C, 'M', u'j'), + (0x2C7D, 'M', u'v'), + (0x2C7E, 'M', u'ȿ'), + (0x2C7F, 'M', u'ɀ'), + (0x2C80, 'M', u'ⲁ'), + (0x2C81, 'V'), + (0x2C82, 'M', u'ⲃ'), + (0x2C83, 'V'), + (0x2C84, 'M', u'ⲅ'), + (0x2C85, 'V'), + (0x2C86, 'M', u'ⲇ'), + (0x2C87, 'V'), + (0x2C88, 'M', u'ⲉ'), + (0x2C89, 'V'), + (0x2C8A, 'M', u'ⲋ'), + (0x2C8B, 'V'), + (0x2C8C, 'M', u'ⲍ'), + (0x2C8D, 'V'), + (0x2C8E, 'M', u'ⲏ'), + (0x2C8F, 'V'), + (0x2C90, 'M', u'ⲑ'), + (0x2C91, 'V'), + (0x2C92, 'M', u'ⲓ'), + (0x2C93, 'V'), + (0x2C94, 'M', u'ⲕ'), + (0x2C95, 'V'), + (0x2C96, 'M', u'ⲗ'), + (0x2C97, 'V'), + (0x2C98, 'M', u'ⲙ'), + (0x2C99, 'V'), + (0x2C9A, 'M', u'ⲛ'), + (0x2C9B, 'V'), + (0x2C9C, 'M', u'ⲝ'), + (0x2C9D, 'V'), + (0x2C9E, 'M', u'ⲟ'), + (0x2C9F, 'V'), + (0x2CA0, 'M', u'ⲡ'), + (0x2CA1, 'V'), + (0x2CA2, 'M', u'ⲣ'), + (0x2CA3, 'V'), + (0x2CA4, 'M', u'ⲥ'), + (0x2CA5, 'V'), + (0x2CA6, 'M', u'ⲧ'), + (0x2CA7, 'V'), + (0x2CA8, 'M', u'ⲩ'), + (0x2CA9, 'V'), + (0x2CAA, 'M', u'ⲫ'), + (0x2CAB, 'V'), + (0x2CAC, 'M', u'ⲭ'), + (0x2CAD, 'V'), + (0x2CAE, 'M', u'ⲯ'), + (0x2CAF, 'V'), + (0x2CB0, 'M', u'ⲱ'), + (0x2CB1, 'V'), + (0x2CB2, 'M', u'ⲳ'), + (0x2CB3, 'V'), + (0x2CB4, 'M', u'ⲵ'), + (0x2CB5, 'V'), + (0x2CB6, 'M', u'ⲷ'), + (0x2CB7, 'V'), + (0x2CB8, 'M', u'ⲹ'), + (0x2CB9, 'V'), + (0x2CBA, 'M', u'ⲻ'), + (0x2CBB, 'V'), + (0x2CBC, 'M', u'ⲽ'), + (0x2CBD, 'V'), + (0x2CBE, 'M', u'ⲿ'), + (0x2CBF, 'V'), + (0x2CC0, 'M', u'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', u'ⳃ'), + (0x2CC3, 'V'), + (0x2CC4, 'M', u'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', u'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', u'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', u'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', u'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', u'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', u'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', u'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', u'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', u'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', u'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', u'ⳛ'), + ] + +def _seg_26(): + return [ + (0x2CDB, 'V'), + (0x2CDC, 'M', u'ⳝ'), + (0x2CDD, 'V'), + (0x2CDE, 'M', u'ⳟ'), + (0x2CDF, 'V'), + (0x2CE0, 'M', u'ⳡ'), + (0x2CE1, 'V'), + (0x2CE2, 'M', u'ⳣ'), + (0x2CE3, 'V'), + (0x2CEB, 'M', u'ⳬ'), + (0x2CEC, 'V'), + (0x2CED, 'M', u'ⳮ'), + (0x2CEE, 'V'), + (0x2CF2, 'M', u'ⳳ'), + (0x2CF3, 'V'), + (0x2CF4, 'X'), + (0x2CF9, 'V'), + (0x2D26, 'X'), + (0x2D27, 'V'), + (0x2D28, 'X'), + (0x2D2D, 'V'), + (0x2D2E, 'X'), + (0x2D30, 'V'), + (0x2D68, 'X'), + (0x2D6F, 'M', u'ⵡ'), + (0x2D70, 'V'), + (0x2D71, 'X'), + (0x2D7F, 'V'), + (0x2D97, 'X'), + (0x2DA0, 'V'), + (0x2DA7, 'X'), + (0x2DA8, 'V'), + (0x2DAF, 'X'), + (0x2DB0, 'V'), + (0x2DB7, 'X'), + (0x2DB8, 'V'), + (0x2DBF, 'X'), + (0x2DC0, 'V'), + (0x2DC7, 'X'), + (0x2DC8, 'V'), + (0x2DCF, 'X'), + (0x2DD0, 'V'), + (0x2DD7, 'X'), + (0x2DD8, 'V'), + (0x2DDF, 'X'), + (0x2DE0, 'V'), + (0x2E4F, 'X'), + (0x2E80, 'V'), + (0x2E9A, 'X'), + (0x2E9B, 'V'), + (0x2E9F, 'M', u'母'), + (0x2EA0, 'V'), + (0x2EF3, 'M', u'龟'), + (0x2EF4, 'X'), + (0x2F00, 'M', u'一'), + (0x2F01, 'M', u'丨'), + (0x2F02, 'M', u'丶'), + (0x2F03, 'M', u'丿'), + (0x2F04, 'M', u'乙'), + (0x2F05, 'M', u'亅'), + (0x2F06, 'M', u'二'), + (0x2F07, 'M', u'亠'), + (0x2F08, 'M', u'人'), + (0x2F09, 'M', u'儿'), + (0x2F0A, 'M', u'入'), + (0x2F0B, 'M', u'八'), + (0x2F0C, 'M', u'冂'), + (0x2F0D, 'M', u'冖'), + (0x2F0E, 'M', u'冫'), + (0x2F0F, 'M', u'几'), + (0x2F10, 'M', u'凵'), + (0x2F11, 'M', u'刀'), + (0x2F12, 'M', u'力'), + (0x2F13, 'M', u'勹'), + (0x2F14, 'M', u'匕'), + (0x2F15, 'M', u'匚'), + (0x2F16, 'M', u'匸'), + (0x2F17, 'M', u'十'), + (0x2F18, 'M', u'卜'), + (0x2F19, 'M', u'卩'), + (0x2F1A, 'M', u'厂'), + (0x2F1B, 'M', u'厶'), + (0x2F1C, 'M', u'又'), + (0x2F1D, 'M', u'口'), + (0x2F1E, 'M', u'囗'), + (0x2F1F, 'M', u'土'), + (0x2F20, 'M', u'士'), + (0x2F21, 'M', u'夂'), + (0x2F22, 'M', u'夊'), + (0x2F23, 'M', u'夕'), + (0x2F24, 'M', u'大'), + (0x2F25, 'M', u'女'), + (0x2F26, 'M', u'子'), + (0x2F27, 'M', u'宀'), + (0x2F28, 'M', u'寸'), + (0x2F29, 'M', u'小'), + (0x2F2A, 'M', u'尢'), + (0x2F2B, 'M', u'尸'), + (0x2F2C, 'M', u'屮'), + (0x2F2D, 'M', u'山'), + ] + +def _seg_27(): + return [ + (0x2F2E, 'M', u'巛'), + (0x2F2F, 'M', u'工'), + (0x2F30, 'M', u'己'), + (0x2F31, 'M', u'巾'), + (0x2F32, 'M', u'干'), + (0x2F33, 'M', u'幺'), + (0x2F34, 'M', u'广'), + (0x2F35, 'M', u'廴'), + (0x2F36, 'M', u'廾'), + (0x2F37, 'M', u'弋'), + (0x2F38, 'M', u'弓'), + (0x2F39, 'M', u'彐'), + (0x2F3A, 'M', u'彡'), + (0x2F3B, 'M', u'彳'), + (0x2F3C, 'M', u'心'), + (0x2F3D, 'M', u'戈'), + (0x2F3E, 'M', u'戶'), + (0x2F3F, 'M', u'手'), + (0x2F40, 'M', u'支'), + (0x2F41, 'M', u'攴'), + (0x2F42, 'M', u'文'), + (0x2F43, 'M', u'斗'), + (0x2F44, 'M', u'斤'), + (0x2F45, 'M', u'方'), + (0x2F46, 'M', u'无'), + (0x2F47, 'M', u'日'), + (0x2F48, 'M', u'曰'), + (0x2F49, 'M', u'月'), + (0x2F4A, 'M', u'木'), + (0x2F4B, 'M', u'欠'), + (0x2F4C, 'M', u'止'), + (0x2F4D, 'M', u'歹'), + (0x2F4E, 'M', u'殳'), + (0x2F4F, 'M', u'毋'), + (0x2F50, 'M', u'比'), + (0x2F51, 'M', u'毛'), + (0x2F52, 'M', u'氏'), + (0x2F53, 'M', u'气'), + (0x2F54, 'M', u'水'), + (0x2F55, 'M', u'火'), + (0x2F56, 'M', u'爪'), + (0x2F57, 'M', u'父'), + (0x2F58, 'M', u'爻'), + (0x2F59, 'M', u'爿'), + (0x2F5A, 'M', u'片'), + (0x2F5B, 'M', u'牙'), + (0x2F5C, 'M', u'牛'), + (0x2F5D, 'M', u'犬'), + (0x2F5E, 'M', u'玄'), + (0x2F5F, 'M', u'玉'), + (0x2F60, 'M', u'瓜'), + (0x2F61, 'M', u'瓦'), + (0x2F62, 'M', u'甘'), + (0x2F63, 'M', u'生'), + (0x2F64, 'M', u'用'), + (0x2F65, 'M', u'田'), + (0x2F66, 'M', u'疋'), + (0x2F67, 'M', u'疒'), + (0x2F68, 'M', u'癶'), + (0x2F69, 'M', u'白'), + (0x2F6A, 'M', u'皮'), + (0x2F6B, 'M', u'皿'), + (0x2F6C, 'M', u'目'), + (0x2F6D, 'M', u'矛'), + (0x2F6E, 'M', u'矢'), + (0x2F6F, 'M', u'石'), + (0x2F70, 'M', u'示'), + (0x2F71, 'M', u'禸'), + (0x2F72, 'M', u'禾'), + (0x2F73, 'M', u'穴'), + (0x2F74, 'M', u'立'), + (0x2F75, 'M', u'竹'), + (0x2F76, 'M', u'米'), + (0x2F77, 'M', u'糸'), + (0x2F78, 'M', u'缶'), + (0x2F79, 'M', u'网'), + (0x2F7A, 'M', u'羊'), + (0x2F7B, 'M', u'羽'), + (0x2F7C, 'M', u'老'), + (0x2F7D, 'M', u'而'), + (0x2F7E, 'M', u'耒'), + (0x2F7F, 'M', u'耳'), + (0x2F80, 'M', u'聿'), + (0x2F81, 'M', u'肉'), + (0x2F82, 'M', u'臣'), + (0x2F83, 'M', u'自'), + (0x2F84, 'M', u'至'), + (0x2F85, 'M', u'臼'), + (0x2F86, 'M', u'舌'), + (0x2F87, 'M', u'舛'), + (0x2F88, 'M', u'舟'), + (0x2F89, 'M', u'艮'), + (0x2F8A, 'M', u'色'), + (0x2F8B, 'M', u'艸'), + (0x2F8C, 'M', u'虍'), + (0x2F8D, 'M', u'虫'), + (0x2F8E, 'M', u'血'), + (0x2F8F, 'M', u'行'), + (0x2F90, 'M', u'衣'), + (0x2F91, 'M', u'襾'), + ] + +def _seg_28(): + return [ + (0x2F92, 'M', u'見'), + (0x2F93, 'M', u'角'), + (0x2F94, 'M', u'言'), + (0x2F95, 'M', u'谷'), + (0x2F96, 'M', u'豆'), + (0x2F97, 'M', u'豕'), + (0x2F98, 'M', u'豸'), + (0x2F99, 'M', u'貝'), + (0x2F9A, 'M', u'赤'), + (0x2F9B, 'M', u'走'), + (0x2F9C, 'M', u'足'), + (0x2F9D, 'M', u'身'), + (0x2F9E, 'M', u'車'), + (0x2F9F, 'M', u'辛'), + (0x2FA0, 'M', u'辰'), + (0x2FA1, 'M', u'辵'), + (0x2FA2, 'M', u'邑'), + (0x2FA3, 'M', u'酉'), + (0x2FA4, 'M', u'釆'), + (0x2FA5, 'M', u'里'), + (0x2FA6, 'M', u'金'), + (0x2FA7, 'M', u'長'), + (0x2FA8, 'M', u'門'), + (0x2FA9, 'M', u'阜'), + (0x2FAA, 'M', u'隶'), + (0x2FAB, 'M', u'隹'), + (0x2FAC, 'M', u'雨'), + (0x2FAD, 'M', u'靑'), + (0x2FAE, 'M', u'非'), + (0x2FAF, 'M', u'面'), + (0x2FB0, 'M', u'革'), + (0x2FB1, 'M', u'韋'), + (0x2FB2, 'M', u'韭'), + (0x2FB3, 'M', u'音'), + (0x2FB4, 'M', u'頁'), + (0x2FB5, 'M', u'風'), + (0x2FB6, 'M', u'飛'), + (0x2FB7, 'M', u'食'), + (0x2FB8, 'M', u'首'), + (0x2FB9, 'M', u'香'), + (0x2FBA, 'M', u'馬'), + (0x2FBB, 'M', u'骨'), + (0x2FBC, 'M', u'高'), + (0x2FBD, 'M', u'髟'), + (0x2FBE, 'M', u'鬥'), + (0x2FBF, 'M', u'鬯'), + (0x2FC0, 'M', u'鬲'), + (0x2FC1, 'M', u'鬼'), + (0x2FC2, 'M', u'魚'), + (0x2FC3, 'M', u'鳥'), + (0x2FC4, 'M', u'鹵'), + (0x2FC5, 'M', u'鹿'), + (0x2FC6, 'M', u'麥'), + (0x2FC7, 'M', u'麻'), + (0x2FC8, 'M', u'黃'), + (0x2FC9, 'M', u'黍'), + (0x2FCA, 'M', u'黑'), + (0x2FCB, 'M', u'黹'), + (0x2FCC, 'M', u'黽'), + (0x2FCD, 'M', u'鼎'), + (0x2FCE, 'M', u'鼓'), + (0x2FCF, 'M', u'鼠'), + (0x2FD0, 'M', u'鼻'), + (0x2FD1, 'M', u'齊'), + (0x2FD2, 'M', u'齒'), + (0x2FD3, 'M', u'龍'), + (0x2FD4, 'M', u'龜'), + (0x2FD5, 'M', u'龠'), + (0x2FD6, 'X'), + (0x3000, '3', u' '), + (0x3001, 'V'), + (0x3002, 'M', u'.'), + (0x3003, 'V'), + (0x3036, 'M', u'〒'), + (0x3037, 'V'), + (0x3038, 'M', u'十'), + (0x3039, 'M', u'卄'), + (0x303A, 'M', u'卅'), + (0x303B, 'V'), + (0x3040, 'X'), + (0x3041, 'V'), + (0x3097, 'X'), + (0x3099, 'V'), + (0x309B, '3', u' ゙'), + (0x309C, '3', u' ゚'), + (0x309D, 'V'), + (0x309F, 'M', u'より'), + (0x30A0, 'V'), + (0x30FF, 'M', u'コト'), + (0x3100, 'X'), + (0x3105, 'V'), + (0x3130, 'X'), + (0x3131, 'M', u'ᄀ'), + (0x3132, 'M', u'ᄁ'), + (0x3133, 'M', u'ᆪ'), + (0x3134, 'M', u'ᄂ'), + (0x3135, 'M', u'ᆬ'), + (0x3136, 'M', u'ᆭ'), + (0x3137, 'M', u'ᄃ'), + (0x3138, 'M', u'ᄄ'), + ] + +def _seg_29(): + return [ + (0x3139, 'M', u'ᄅ'), + (0x313A, 'M', u'ᆰ'), + (0x313B, 'M', u'ᆱ'), + (0x313C, 'M', u'ᆲ'), + (0x313D, 'M', u'ᆳ'), + (0x313E, 'M', u'ᆴ'), + (0x313F, 'M', u'ᆵ'), + (0x3140, 'M', u'ᄚ'), + (0x3141, 'M', u'ᄆ'), + (0x3142, 'M', u'ᄇ'), + (0x3143, 'M', u'ᄈ'), + (0x3144, 'M', u'ᄡ'), + (0x3145, 'M', u'ᄉ'), + (0x3146, 'M', u'ᄊ'), + (0x3147, 'M', u'ᄋ'), + (0x3148, 'M', u'ᄌ'), + (0x3149, 'M', u'ᄍ'), + (0x314A, 'M', u'ᄎ'), + (0x314B, 'M', u'ᄏ'), + (0x314C, 'M', u'ᄐ'), + (0x314D, 'M', u'ᄑ'), + (0x314E, 'M', u'ᄒ'), + (0x314F, 'M', u'ᅡ'), + (0x3150, 'M', u'ᅢ'), + (0x3151, 'M', u'ᅣ'), + (0x3152, 'M', u'ᅤ'), + (0x3153, 'M', u'ᅥ'), + (0x3154, 'M', u'ᅦ'), + (0x3155, 'M', u'ᅧ'), + (0x3156, 'M', u'ᅨ'), + (0x3157, 'M', u'ᅩ'), + (0x3158, 'M', u'ᅪ'), + (0x3159, 'M', u'ᅫ'), + (0x315A, 'M', u'ᅬ'), + (0x315B, 'M', u'ᅭ'), + (0x315C, 'M', u'ᅮ'), + (0x315D, 'M', u'ᅯ'), + (0x315E, 'M', u'ᅰ'), + (0x315F, 'M', u'ᅱ'), + (0x3160, 'M', u'ᅲ'), + (0x3161, 'M', u'ᅳ'), + (0x3162, 'M', u'ᅴ'), + (0x3163, 'M', u'ᅵ'), + (0x3164, 'X'), + (0x3165, 'M', u'ᄔ'), + (0x3166, 'M', u'ᄕ'), + (0x3167, 'M', u'ᇇ'), + (0x3168, 'M', u'ᇈ'), + (0x3169, 'M', u'ᇌ'), + (0x316A, 'M', u'ᇎ'), + (0x316B, 'M', u'ᇓ'), + (0x316C, 'M', u'ᇗ'), + (0x316D, 'M', u'ᇙ'), + (0x316E, 'M', u'ᄜ'), + (0x316F, 'M', u'ᇝ'), + (0x3170, 'M', u'ᇟ'), + (0x3171, 'M', u'ᄝ'), + (0x3172, 'M', u'ᄞ'), + (0x3173, 'M', u'ᄠ'), + (0x3174, 'M', u'ᄢ'), + (0x3175, 'M', u'ᄣ'), + (0x3176, 'M', u'ᄧ'), + (0x3177, 'M', u'ᄩ'), + (0x3178, 'M', u'ᄫ'), + (0x3179, 'M', u'ᄬ'), + (0x317A, 'M', u'ᄭ'), + (0x317B, 'M', u'ᄮ'), + (0x317C, 'M', u'ᄯ'), + (0x317D, 'M', u'ᄲ'), + (0x317E, 'M', u'ᄶ'), + (0x317F, 'M', u'ᅀ'), + (0x3180, 'M', u'ᅇ'), + (0x3181, 'M', u'ᅌ'), + (0x3182, 'M', u'ᇱ'), + (0x3183, 'M', u'ᇲ'), + (0x3184, 'M', u'ᅗ'), + (0x3185, 'M', u'ᅘ'), + (0x3186, 'M', u'ᅙ'), + (0x3187, 'M', u'ᆄ'), + (0x3188, 'M', u'ᆅ'), + (0x3189, 'M', u'ᆈ'), + (0x318A, 'M', u'ᆑ'), + (0x318B, 'M', u'ᆒ'), + (0x318C, 'M', u'ᆔ'), + (0x318D, 'M', u'ᆞ'), + (0x318E, 'M', u'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', u'一'), + (0x3193, 'M', u'二'), + (0x3194, 'M', u'三'), + (0x3195, 'M', u'四'), + (0x3196, 'M', u'上'), + (0x3197, 'M', u'中'), + (0x3198, 'M', u'下'), + (0x3199, 'M', u'甲'), + (0x319A, 'M', u'乙'), + (0x319B, 'M', u'丙'), + (0x319C, 'M', u'丁'), + (0x319D, 'M', u'天'), + ] + +def _seg_30(): + return [ + (0x319E, 'M', u'地'), + (0x319F, 'M', u'人'), + (0x31A0, 'V'), + (0x31BB, 'X'), + (0x31C0, 'V'), + (0x31E4, 'X'), + (0x31F0, 'V'), + (0x3200, '3', u'(ᄀ)'), + (0x3201, '3', u'(ᄂ)'), + (0x3202, '3', u'(ᄃ)'), + (0x3203, '3', u'(ᄅ)'), + (0x3204, '3', u'(ᄆ)'), + (0x3205, '3', u'(ᄇ)'), + (0x3206, '3', u'(ᄉ)'), + (0x3207, '3', u'(ᄋ)'), + (0x3208, '3', u'(ᄌ)'), + (0x3209, '3', u'(ᄎ)'), + (0x320A, '3', u'(ᄏ)'), + (0x320B, '3', u'(ᄐ)'), + (0x320C, '3', u'(ᄑ)'), + (0x320D, '3', u'(ᄒ)'), + (0x320E, '3', u'(가)'), + (0x320F, '3', u'(나)'), + (0x3210, '3', u'(다)'), + (0x3211, '3', u'(라)'), + (0x3212, '3', u'(마)'), + (0x3213, '3', u'(바)'), + (0x3214, '3', u'(사)'), + (0x3215, '3', u'(아)'), + (0x3216, '3', u'(자)'), + (0x3217, '3', u'(차)'), + (0x3218, '3', u'(카)'), + (0x3219, '3', u'(타)'), + (0x321A, '3', u'(파)'), + (0x321B, '3', u'(하)'), + (0x321C, '3', u'(주)'), + (0x321D, '3', u'(오전)'), + (0x321E, '3', u'(오후)'), + (0x321F, 'X'), + (0x3220, '3', u'(一)'), + (0x3221, '3', u'(二)'), + (0x3222, '3', u'(三)'), + (0x3223, '3', u'(四)'), + (0x3224, '3', u'(五)'), + (0x3225, '3', u'(六)'), + (0x3226, '3', u'(七)'), + (0x3227, '3', u'(八)'), + (0x3228, '3', u'(九)'), + (0x3229, '3', u'(十)'), + (0x322A, '3', u'(月)'), + (0x322B, '3', u'(火)'), + (0x322C, '3', u'(水)'), + (0x322D, '3', u'(木)'), + (0x322E, '3', u'(金)'), + (0x322F, '3', u'(土)'), + (0x3230, '3', u'(日)'), + (0x3231, '3', u'(株)'), + (0x3232, '3', u'(有)'), + (0x3233, '3', u'(社)'), + (0x3234, '3', u'(名)'), + (0x3235, '3', u'(特)'), + (0x3236, '3', u'(財)'), + (0x3237, '3', u'(祝)'), + (0x3238, '3', u'(労)'), + (0x3239, '3', u'(代)'), + (0x323A, '3', u'(呼)'), + (0x323B, '3', u'(学)'), + (0x323C, '3', u'(監)'), + (0x323D, '3', u'(企)'), + (0x323E, '3', u'(資)'), + (0x323F, '3', u'(協)'), + (0x3240, '3', u'(祭)'), + (0x3241, '3', u'(休)'), + (0x3242, '3', u'(自)'), + (0x3243, '3', u'(至)'), + (0x3244, 'M', u'問'), + (0x3245, 'M', u'幼'), + (0x3246, 'M', u'文'), + (0x3247, 'M', u'箏'), + (0x3248, 'V'), + (0x3250, 'M', u'pte'), + (0x3251, 'M', u'21'), + (0x3252, 'M', u'22'), + (0x3253, 'M', u'23'), + (0x3254, 'M', u'24'), + (0x3255, 'M', u'25'), + (0x3256, 'M', u'26'), + (0x3257, 'M', u'27'), + (0x3258, 'M', u'28'), + (0x3259, 'M', u'29'), + (0x325A, 'M', u'30'), + (0x325B, 'M', u'31'), + (0x325C, 'M', u'32'), + (0x325D, 'M', u'33'), + (0x325E, 'M', u'34'), + (0x325F, 'M', u'35'), + (0x3260, 'M', u'ᄀ'), + (0x3261, 'M', u'ᄂ'), + (0x3262, 'M', u'ᄃ'), + (0x3263, 'M', u'ᄅ'), + ] + +def _seg_31(): + return [ + (0x3264, 'M', u'ᄆ'), + (0x3265, 'M', u'ᄇ'), + (0x3266, 'M', u'ᄉ'), + (0x3267, 'M', u'ᄋ'), + (0x3268, 'M', u'ᄌ'), + (0x3269, 'M', u'ᄎ'), + (0x326A, 'M', u'ᄏ'), + (0x326B, 'M', u'ᄐ'), + (0x326C, 'M', u'ᄑ'), + (0x326D, 'M', u'ᄒ'), + (0x326E, 'M', u'가'), + (0x326F, 'M', u'나'), + (0x3270, 'M', u'다'), + (0x3271, 'M', u'라'), + (0x3272, 'M', u'마'), + (0x3273, 'M', u'바'), + (0x3274, 'M', u'사'), + (0x3275, 'M', u'아'), + (0x3276, 'M', u'자'), + (0x3277, 'M', u'차'), + (0x3278, 'M', u'카'), + (0x3279, 'M', u'타'), + (0x327A, 'M', u'파'), + (0x327B, 'M', u'하'), + (0x327C, 'M', u'참고'), + (0x327D, 'M', u'주의'), + (0x327E, 'M', u'우'), + (0x327F, 'V'), + (0x3280, 'M', u'一'), + (0x3281, 'M', u'二'), + (0x3282, 'M', u'三'), + (0x3283, 'M', u'四'), + (0x3284, 'M', u'五'), + (0x3285, 'M', u'六'), + (0x3286, 'M', u'七'), + (0x3287, 'M', u'八'), + (0x3288, 'M', u'九'), + (0x3289, 'M', u'十'), + (0x328A, 'M', u'月'), + (0x328B, 'M', u'火'), + (0x328C, 'M', u'水'), + (0x328D, 'M', u'木'), + (0x328E, 'M', u'金'), + (0x328F, 'M', u'土'), + (0x3290, 'M', u'日'), + (0x3291, 'M', u'株'), + (0x3292, 'M', u'有'), + (0x3293, 'M', u'社'), + (0x3294, 'M', u'名'), + (0x3295, 'M', u'特'), + (0x3296, 'M', u'財'), + (0x3297, 'M', u'祝'), + (0x3298, 'M', u'労'), + (0x3299, 'M', u'秘'), + (0x329A, 'M', u'男'), + (0x329B, 'M', u'女'), + (0x329C, 'M', u'適'), + (0x329D, 'M', u'優'), + (0x329E, 'M', u'印'), + (0x329F, 'M', u'注'), + (0x32A0, 'M', u'項'), + (0x32A1, 'M', u'休'), + (0x32A2, 'M', u'写'), + (0x32A3, 'M', u'正'), + (0x32A4, 'M', u'上'), + (0x32A5, 'M', u'中'), + (0x32A6, 'M', u'下'), + (0x32A7, 'M', u'左'), + (0x32A8, 'M', u'右'), + (0x32A9, 'M', u'医'), + (0x32AA, 'M', u'宗'), + (0x32AB, 'M', u'学'), + (0x32AC, 'M', u'監'), + (0x32AD, 'M', u'企'), + (0x32AE, 'M', u'資'), + (0x32AF, 'M', u'協'), + (0x32B0, 'M', u'夜'), + (0x32B1, 'M', u'36'), + (0x32B2, 'M', u'37'), + (0x32B3, 'M', u'38'), + (0x32B4, 'M', u'39'), + (0x32B5, 'M', u'40'), + (0x32B6, 'M', u'41'), + (0x32B7, 'M', u'42'), + (0x32B8, 'M', u'43'), + (0x32B9, 'M', u'44'), + (0x32BA, 'M', u'45'), + (0x32BB, 'M', u'46'), + (0x32BC, 'M', u'47'), + (0x32BD, 'M', u'48'), + (0x32BE, 'M', u'49'), + (0x32BF, 'M', u'50'), + (0x32C0, 'M', u'1月'), + (0x32C1, 'M', u'2月'), + (0x32C2, 'M', u'3月'), + (0x32C3, 'M', u'4月'), + (0x32C4, 'M', u'5月'), + (0x32C5, 'M', u'6月'), + (0x32C6, 'M', u'7月'), + (0x32C7, 'M', u'8月'), + ] + +def _seg_32(): + return [ + (0x32C8, 'M', u'9月'), + (0x32C9, 'M', u'10月'), + (0x32CA, 'M', u'11月'), + (0x32CB, 'M', u'12月'), + (0x32CC, 'M', u'hg'), + (0x32CD, 'M', u'erg'), + (0x32CE, 'M', u'ev'), + (0x32CF, 'M', u'ltd'), + (0x32D0, 'M', u'ア'), + (0x32D1, 'M', u'イ'), + (0x32D2, 'M', u'ウ'), + (0x32D3, 'M', u'エ'), + (0x32D4, 'M', u'オ'), + (0x32D5, 'M', u'カ'), + (0x32D6, 'M', u'キ'), + (0x32D7, 'M', u'ク'), + (0x32D8, 'M', u'ケ'), + (0x32D9, 'M', u'コ'), + (0x32DA, 'M', u'サ'), + (0x32DB, 'M', u'シ'), + (0x32DC, 'M', u'ス'), + (0x32DD, 'M', u'セ'), + (0x32DE, 'M', u'ソ'), + (0x32DF, 'M', u'タ'), + (0x32E0, 'M', u'チ'), + (0x32E1, 'M', u'ツ'), + (0x32E2, 'M', u'テ'), + (0x32E3, 'M', u'ト'), + (0x32E4, 'M', u'ナ'), + (0x32E5, 'M', u'ニ'), + (0x32E6, 'M', u'ヌ'), + (0x32E7, 'M', u'ネ'), + (0x32E8, 'M', u'ノ'), + (0x32E9, 'M', u'ハ'), + (0x32EA, 'M', u'ヒ'), + (0x32EB, 'M', u'フ'), + (0x32EC, 'M', u'ヘ'), + (0x32ED, 'M', u'ホ'), + (0x32EE, 'M', u'マ'), + (0x32EF, 'M', u'ミ'), + (0x32F0, 'M', u'ム'), + (0x32F1, 'M', u'メ'), + (0x32F2, 'M', u'モ'), + (0x32F3, 'M', u'ヤ'), + (0x32F4, 'M', u'ユ'), + (0x32F5, 'M', u'ヨ'), + (0x32F6, 'M', u'ラ'), + (0x32F7, 'M', u'リ'), + (0x32F8, 'M', u'ル'), + (0x32F9, 'M', u'レ'), + (0x32FA, 'M', u'ロ'), + (0x32FB, 'M', u'ワ'), + (0x32FC, 'M', u'ヰ'), + (0x32FD, 'M', u'ヱ'), + (0x32FE, 'M', u'ヲ'), + (0x32FF, 'X'), + (0x3300, 'M', u'アパート'), + (0x3301, 'M', u'アルファ'), + (0x3302, 'M', u'アンペア'), + (0x3303, 'M', u'アール'), + (0x3304, 'M', u'イニング'), + (0x3305, 'M', u'インチ'), + (0x3306, 'M', u'ウォン'), + (0x3307, 'M', u'エスクード'), + (0x3308, 'M', u'エーカー'), + (0x3309, 'M', u'オンス'), + (0x330A, 'M', u'オーム'), + (0x330B, 'M', u'カイリ'), + (0x330C, 'M', u'カラット'), + (0x330D, 'M', u'カロリー'), + (0x330E, 'M', u'ガロン'), + (0x330F, 'M', u'ガンマ'), + (0x3310, 'M', u'ギガ'), + (0x3311, 'M', u'ギニー'), + (0x3312, 'M', u'キュリー'), + (0x3313, 'M', u'ギルダー'), + (0x3314, 'M', u'キロ'), + (0x3315, 'M', u'キログラム'), + (0x3316, 'M', u'キロメートル'), + (0x3317, 'M', u'キロワット'), + (0x3318, 'M', u'グラム'), + (0x3319, 'M', u'グラムトン'), + (0x331A, 'M', u'クルゼイロ'), + (0x331B, 'M', u'クローネ'), + (0x331C, 'M', u'ケース'), + (0x331D, 'M', u'コルナ'), + (0x331E, 'M', u'コーポ'), + (0x331F, 'M', u'サイクル'), + (0x3320, 'M', u'サンチーム'), + (0x3321, 'M', u'シリング'), + (0x3322, 'M', u'センチ'), + (0x3323, 'M', u'セント'), + (0x3324, 'M', u'ダース'), + (0x3325, 'M', u'デシ'), + (0x3326, 'M', u'ドル'), + (0x3327, 'M', u'トン'), + (0x3328, 'M', u'ナノ'), + (0x3329, 'M', u'ノット'), + (0x332A, 'M', u'ハイツ'), + (0x332B, 'M', u'パーセント'), + ] + +def _seg_33(): + return [ + (0x332C, 'M', u'パーツ'), + (0x332D, 'M', u'バーレル'), + (0x332E, 'M', u'ピアストル'), + (0x332F, 'M', u'ピクル'), + (0x3330, 'M', u'ピコ'), + (0x3331, 'M', u'ビル'), + (0x3332, 'M', u'ファラッド'), + (0x3333, 'M', u'フィート'), + (0x3334, 'M', u'ブッシェル'), + (0x3335, 'M', u'フラン'), + (0x3336, 'M', u'ヘクタール'), + (0x3337, 'M', u'ペソ'), + (0x3338, 'M', u'ペニヒ'), + (0x3339, 'M', u'ヘルツ'), + (0x333A, 'M', u'ペンス'), + (0x333B, 'M', u'ページ'), + (0x333C, 'M', u'ベータ'), + (0x333D, 'M', u'ポイント'), + (0x333E, 'M', u'ボルト'), + (0x333F, 'M', u'ホン'), + (0x3340, 'M', u'ポンド'), + (0x3341, 'M', u'ホール'), + (0x3342, 'M', u'ホーン'), + (0x3343, 'M', u'マイクロ'), + (0x3344, 'M', u'マイル'), + (0x3345, 'M', u'マッハ'), + (0x3346, 'M', u'マルク'), + (0x3347, 'M', u'マンション'), + (0x3348, 'M', u'ミクロン'), + (0x3349, 'M', u'ミリ'), + (0x334A, 'M', u'ミリバール'), + (0x334B, 'M', u'メガ'), + (0x334C, 'M', u'メガトン'), + (0x334D, 'M', u'メートル'), + (0x334E, 'M', u'ヤード'), + (0x334F, 'M', u'ヤール'), + (0x3350, 'M', u'ユアン'), + (0x3351, 'M', u'リットル'), + (0x3352, 'M', u'リラ'), + (0x3353, 'M', u'ルピー'), + (0x3354, 'M', u'ルーブル'), + (0x3355, 'M', u'レム'), + (0x3356, 'M', u'レントゲン'), + (0x3357, 'M', u'ワット'), + (0x3358, 'M', u'0点'), + (0x3359, 'M', u'1点'), + (0x335A, 'M', u'2点'), + (0x335B, 'M', u'3点'), + (0x335C, 'M', u'4点'), + (0x335D, 'M', u'5点'), + (0x335E, 'M', u'6点'), + (0x335F, 'M', u'7点'), + (0x3360, 'M', u'8点'), + (0x3361, 'M', u'9点'), + (0x3362, 'M', u'10点'), + (0x3363, 'M', u'11点'), + (0x3364, 'M', u'12点'), + (0x3365, 'M', u'13点'), + (0x3366, 'M', u'14点'), + (0x3367, 'M', u'15点'), + (0x3368, 'M', u'16点'), + (0x3369, 'M', u'17点'), + (0x336A, 'M', u'18点'), + (0x336B, 'M', u'19点'), + (0x336C, 'M', u'20点'), + (0x336D, 'M', u'21点'), + (0x336E, 'M', u'22点'), + (0x336F, 'M', u'23点'), + (0x3370, 'M', u'24点'), + (0x3371, 'M', u'hpa'), + (0x3372, 'M', u'da'), + (0x3373, 'M', u'au'), + (0x3374, 'M', u'bar'), + (0x3375, 'M', u'ov'), + (0x3376, 'M', u'pc'), + (0x3377, 'M', u'dm'), + (0x3378, 'M', u'dm2'), + (0x3379, 'M', u'dm3'), + (0x337A, 'M', u'iu'), + (0x337B, 'M', u'平成'), + (0x337C, 'M', u'昭和'), + (0x337D, 'M', u'大正'), + (0x337E, 'M', u'明治'), + (0x337F, 'M', u'株式会社'), + (0x3380, 'M', u'pa'), + (0x3381, 'M', u'na'), + (0x3382, 'M', u'μa'), + (0x3383, 'M', u'ma'), + (0x3384, 'M', u'ka'), + (0x3385, 'M', u'kb'), + (0x3386, 'M', u'mb'), + (0x3387, 'M', u'gb'), + (0x3388, 'M', u'cal'), + (0x3389, 'M', u'kcal'), + (0x338A, 'M', u'pf'), + (0x338B, 'M', u'nf'), + (0x338C, 'M', u'μf'), + (0x338D, 'M', u'μg'), + (0x338E, 'M', u'mg'), + (0x338F, 'M', u'kg'), + ] + +def _seg_34(): + return [ + (0x3390, 'M', u'hz'), + (0x3391, 'M', u'khz'), + (0x3392, 'M', u'mhz'), + (0x3393, 'M', u'ghz'), + (0x3394, 'M', u'thz'), + (0x3395, 'M', u'μl'), + (0x3396, 'M', u'ml'), + (0x3397, 'M', u'dl'), + (0x3398, 'M', u'kl'), + (0x3399, 'M', u'fm'), + (0x339A, 'M', u'nm'), + (0x339B, 'M', u'μm'), + (0x339C, 'M', u'mm'), + (0x339D, 'M', u'cm'), + (0x339E, 'M', u'km'), + (0x339F, 'M', u'mm2'), + (0x33A0, 'M', u'cm2'), + (0x33A1, 'M', u'm2'), + (0x33A2, 'M', u'km2'), + (0x33A3, 'M', u'mm3'), + (0x33A4, 'M', u'cm3'), + (0x33A5, 'M', u'm3'), + (0x33A6, 'M', u'km3'), + (0x33A7, 'M', u'm∕s'), + (0x33A8, 'M', u'm∕s2'), + (0x33A9, 'M', u'pa'), + (0x33AA, 'M', u'kpa'), + (0x33AB, 'M', u'mpa'), + (0x33AC, 'M', u'gpa'), + (0x33AD, 'M', u'rad'), + (0x33AE, 'M', u'rad∕s'), + (0x33AF, 'M', u'rad∕s2'), + (0x33B0, 'M', u'ps'), + (0x33B1, 'M', u'ns'), + (0x33B2, 'M', u'μs'), + (0x33B3, 'M', u'ms'), + (0x33B4, 'M', u'pv'), + (0x33B5, 'M', u'nv'), + (0x33B6, 'M', u'μv'), + (0x33B7, 'M', u'mv'), + (0x33B8, 'M', u'kv'), + (0x33B9, 'M', u'mv'), + (0x33BA, 'M', u'pw'), + (0x33BB, 'M', u'nw'), + (0x33BC, 'M', u'μw'), + (0x33BD, 'M', u'mw'), + (0x33BE, 'M', u'kw'), + (0x33BF, 'M', u'mw'), + (0x33C0, 'M', u'kω'), + (0x33C1, 'M', u'mω'), + (0x33C2, 'X'), + (0x33C3, 'M', u'bq'), + (0x33C4, 'M', u'cc'), + (0x33C5, 'M', u'cd'), + (0x33C6, 'M', u'c∕kg'), + (0x33C7, 'X'), + (0x33C8, 'M', u'db'), + (0x33C9, 'M', u'gy'), + (0x33CA, 'M', u'ha'), + (0x33CB, 'M', u'hp'), + (0x33CC, 'M', u'in'), + (0x33CD, 'M', u'kk'), + (0x33CE, 'M', u'km'), + (0x33CF, 'M', u'kt'), + (0x33D0, 'M', u'lm'), + (0x33D1, 'M', u'ln'), + (0x33D2, 'M', u'log'), + (0x33D3, 'M', u'lx'), + (0x33D4, 'M', u'mb'), + (0x33D5, 'M', u'mil'), + (0x33D6, 'M', u'mol'), + (0x33D7, 'M', u'ph'), + (0x33D8, 'X'), + (0x33D9, 'M', u'ppm'), + (0x33DA, 'M', u'pr'), + (0x33DB, 'M', u'sr'), + (0x33DC, 'M', u'sv'), + (0x33DD, 'M', u'wb'), + (0x33DE, 'M', u'v∕m'), + (0x33DF, 'M', u'a∕m'), + (0x33E0, 'M', u'1日'), + (0x33E1, 'M', u'2日'), + (0x33E2, 'M', u'3日'), + (0x33E3, 'M', u'4日'), + (0x33E4, 'M', u'5日'), + (0x33E5, 'M', u'6日'), + (0x33E6, 'M', u'7日'), + (0x33E7, 'M', u'8日'), + (0x33E8, 'M', u'9日'), + (0x33E9, 'M', u'10日'), + (0x33EA, 'M', u'11日'), + (0x33EB, 'M', u'12日'), + (0x33EC, 'M', u'13日'), + (0x33ED, 'M', u'14日'), + (0x33EE, 'M', u'15日'), + (0x33EF, 'M', u'16日'), + (0x33F0, 'M', u'17日'), + (0x33F1, 'M', u'18日'), + (0x33F2, 'M', u'19日'), + (0x33F3, 'M', u'20日'), + ] + +def _seg_35(): + return [ + (0x33F4, 'M', u'21日'), + (0x33F5, 'M', u'22日'), + (0x33F6, 'M', u'23日'), + (0x33F7, 'M', u'24日'), + (0x33F8, 'M', u'25日'), + (0x33F9, 'M', u'26日'), + (0x33FA, 'M', u'27日'), + (0x33FB, 'M', u'28日'), + (0x33FC, 'M', u'29日'), + (0x33FD, 'M', u'30日'), + (0x33FE, 'M', u'31日'), + (0x33FF, 'M', u'gal'), + (0x3400, 'V'), + (0x4DB6, 'X'), + (0x4DC0, 'V'), + (0x9FF0, 'X'), + (0xA000, 'V'), + (0xA48D, 'X'), + (0xA490, 'V'), + (0xA4C7, 'X'), + (0xA4D0, 'V'), + (0xA62C, 'X'), + (0xA640, 'M', u'ꙁ'), + (0xA641, 'V'), + (0xA642, 'M', u'ꙃ'), + (0xA643, 'V'), + (0xA644, 'M', u'ꙅ'), + (0xA645, 'V'), + (0xA646, 'M', u'ꙇ'), + (0xA647, 'V'), + (0xA648, 'M', u'ꙉ'), + (0xA649, 'V'), + (0xA64A, 'M', u'ꙋ'), + (0xA64B, 'V'), + (0xA64C, 'M', u'ꙍ'), + (0xA64D, 'V'), + (0xA64E, 'M', u'ꙏ'), + (0xA64F, 'V'), + (0xA650, 'M', u'ꙑ'), + (0xA651, 'V'), + (0xA652, 'M', u'ꙓ'), + (0xA653, 'V'), + (0xA654, 'M', u'ꙕ'), + (0xA655, 'V'), + (0xA656, 'M', u'ꙗ'), + (0xA657, 'V'), + (0xA658, 'M', u'ꙙ'), + (0xA659, 'V'), + (0xA65A, 'M', u'ꙛ'), + (0xA65B, 'V'), + (0xA65C, 'M', u'ꙝ'), + (0xA65D, 'V'), + (0xA65E, 'M', u'ꙟ'), + (0xA65F, 'V'), + (0xA660, 'M', u'ꙡ'), + (0xA661, 'V'), + (0xA662, 'M', u'ꙣ'), + (0xA663, 'V'), + (0xA664, 'M', u'ꙥ'), + (0xA665, 'V'), + (0xA666, 'M', u'ꙧ'), + (0xA667, 'V'), + (0xA668, 'M', u'ꙩ'), + (0xA669, 'V'), + (0xA66A, 'M', u'ꙫ'), + (0xA66B, 'V'), + (0xA66C, 'M', u'ꙭ'), + (0xA66D, 'V'), + (0xA680, 'M', u'ꚁ'), + (0xA681, 'V'), + (0xA682, 'M', u'ꚃ'), + (0xA683, 'V'), + (0xA684, 'M', u'ꚅ'), + (0xA685, 'V'), + (0xA686, 'M', u'ꚇ'), + (0xA687, 'V'), + (0xA688, 'M', u'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', u'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', u'ꚍ'), + (0xA68D, 'V'), + (0xA68E, 'M', u'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', u'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', u'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', u'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', u'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', u'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', u'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', u'ъ'), + (0xA69D, 'M', u'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), + ] + +def _seg_36(): + return [ + (0xA700, 'V'), + (0xA722, 'M', u'ꜣ'), + (0xA723, 'V'), + (0xA724, 'M', u'ꜥ'), + (0xA725, 'V'), + (0xA726, 'M', u'ꜧ'), + (0xA727, 'V'), + (0xA728, 'M', u'ꜩ'), + (0xA729, 'V'), + (0xA72A, 'M', u'ꜫ'), + (0xA72B, 'V'), + (0xA72C, 'M', u'ꜭ'), + (0xA72D, 'V'), + (0xA72E, 'M', u'ꜯ'), + (0xA72F, 'V'), + (0xA732, 'M', u'ꜳ'), + (0xA733, 'V'), + (0xA734, 'M', u'ꜵ'), + (0xA735, 'V'), + (0xA736, 'M', u'ꜷ'), + (0xA737, 'V'), + (0xA738, 'M', u'ꜹ'), + (0xA739, 'V'), + (0xA73A, 'M', u'ꜻ'), + (0xA73B, 'V'), + (0xA73C, 'M', u'ꜽ'), + (0xA73D, 'V'), + (0xA73E, 'M', u'ꜿ'), + (0xA73F, 'V'), + (0xA740, 'M', u'ꝁ'), + (0xA741, 'V'), + (0xA742, 'M', u'ꝃ'), + (0xA743, 'V'), + (0xA744, 'M', u'ꝅ'), + (0xA745, 'V'), + (0xA746, 'M', u'ꝇ'), + (0xA747, 'V'), + (0xA748, 'M', u'ꝉ'), + (0xA749, 'V'), + (0xA74A, 'M', u'ꝋ'), + (0xA74B, 'V'), + (0xA74C, 'M', u'ꝍ'), + (0xA74D, 'V'), + (0xA74E, 'M', u'ꝏ'), + (0xA74F, 'V'), + (0xA750, 'M', u'ꝑ'), + (0xA751, 'V'), + (0xA752, 'M', u'ꝓ'), + (0xA753, 'V'), + (0xA754, 'M', u'ꝕ'), + (0xA755, 'V'), + (0xA756, 'M', u'ꝗ'), + (0xA757, 'V'), + (0xA758, 'M', u'ꝙ'), + (0xA759, 'V'), + (0xA75A, 'M', u'ꝛ'), + (0xA75B, 'V'), + (0xA75C, 'M', u'ꝝ'), + (0xA75D, 'V'), + (0xA75E, 'M', u'ꝟ'), + (0xA75F, 'V'), + (0xA760, 'M', u'ꝡ'), + (0xA761, 'V'), + (0xA762, 'M', u'ꝣ'), + (0xA763, 'V'), + (0xA764, 'M', u'ꝥ'), + (0xA765, 'V'), + (0xA766, 'M', u'ꝧ'), + (0xA767, 'V'), + (0xA768, 'M', u'ꝩ'), + (0xA769, 'V'), + (0xA76A, 'M', u'ꝫ'), + (0xA76B, 'V'), + (0xA76C, 'M', u'ꝭ'), + (0xA76D, 'V'), + (0xA76E, 'M', u'ꝯ'), + (0xA76F, 'V'), + (0xA770, 'M', u'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', u'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', u'ꝼ'), + (0xA77C, 'V'), + (0xA77D, 'M', u'ᵹ'), + (0xA77E, 'M', u'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', u'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', u'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', u'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', u'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', u'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', u'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', u'ꞑ'), + (0xA791, 'V'), + ] + +def _seg_37(): + return [ + (0xA792, 'M', u'ꞓ'), + (0xA793, 'V'), + (0xA796, 'M', u'ꞗ'), + (0xA797, 'V'), + (0xA798, 'M', u'ꞙ'), + (0xA799, 'V'), + (0xA79A, 'M', u'ꞛ'), + (0xA79B, 'V'), + (0xA79C, 'M', u'ꞝ'), + (0xA79D, 'V'), + (0xA79E, 'M', u'ꞟ'), + (0xA79F, 'V'), + (0xA7A0, 'M', u'ꞡ'), + (0xA7A1, 'V'), + (0xA7A2, 'M', u'ꞣ'), + (0xA7A3, 'V'), + (0xA7A4, 'M', u'ꞥ'), + (0xA7A5, 'V'), + (0xA7A6, 'M', u'ꞧ'), + (0xA7A7, 'V'), + (0xA7A8, 'M', u'ꞩ'), + (0xA7A9, 'V'), + (0xA7AA, 'M', u'ɦ'), + (0xA7AB, 'M', u'ɜ'), + (0xA7AC, 'M', u'ɡ'), + (0xA7AD, 'M', u'ɬ'), + (0xA7AE, 'M', u'ɪ'), + (0xA7AF, 'V'), + (0xA7B0, 'M', u'ʞ'), + (0xA7B1, 'M', u'ʇ'), + (0xA7B2, 'M', u'ʝ'), + (0xA7B3, 'M', u'ꭓ'), + (0xA7B4, 'M', u'ꞵ'), + (0xA7B5, 'V'), + (0xA7B6, 'M', u'ꞷ'), + (0xA7B7, 'V'), + (0xA7B8, 'X'), + (0xA7B9, 'V'), + (0xA7BA, 'X'), + (0xA7F7, 'V'), + (0xA7F8, 'M', u'ħ'), + (0xA7F9, 'M', u'œ'), + (0xA7FA, 'V'), + (0xA82C, 'X'), + (0xA830, 'V'), + (0xA83A, 'X'), + (0xA840, 'V'), + (0xA878, 'X'), + (0xA880, 'V'), + (0xA8C6, 'X'), + (0xA8CE, 'V'), + (0xA8DA, 'X'), + (0xA8E0, 'V'), + (0xA954, 'X'), + (0xA95F, 'V'), + (0xA97D, 'X'), + (0xA980, 'V'), + (0xA9CE, 'X'), + (0xA9CF, 'V'), + (0xA9DA, 'X'), + (0xA9DE, 'V'), + (0xA9FF, 'X'), + (0xAA00, 'V'), + (0xAA37, 'X'), + (0xAA40, 'V'), + (0xAA4E, 'X'), + (0xAA50, 'V'), + (0xAA5A, 'X'), + (0xAA5C, 'V'), + (0xAAC3, 'X'), + (0xAADB, 'V'), + (0xAAF7, 'X'), + (0xAB01, 'V'), + (0xAB07, 'X'), + (0xAB09, 'V'), + (0xAB0F, 'X'), + (0xAB11, 'V'), + (0xAB17, 'X'), + (0xAB20, 'V'), + (0xAB27, 'X'), + (0xAB28, 'V'), + (0xAB2F, 'X'), + (0xAB30, 'V'), + (0xAB5C, 'M', u'ꜧ'), + (0xAB5D, 'M', u'ꬷ'), + (0xAB5E, 'M', u'ɫ'), + (0xAB5F, 'M', u'ꭒ'), + (0xAB60, 'V'), + (0xAB66, 'X'), + (0xAB70, 'M', u'Ꭰ'), + (0xAB71, 'M', u'Ꭱ'), + (0xAB72, 'M', u'Ꭲ'), + (0xAB73, 'M', u'Ꭳ'), + (0xAB74, 'M', u'Ꭴ'), + (0xAB75, 'M', u'Ꭵ'), + (0xAB76, 'M', u'Ꭶ'), + (0xAB77, 'M', u'Ꭷ'), + (0xAB78, 'M', u'Ꭸ'), + (0xAB79, 'M', u'Ꭹ'), + (0xAB7A, 'M', u'Ꭺ'), + ] + +def _seg_38(): + return [ + (0xAB7B, 'M', u'Ꭻ'), + (0xAB7C, 'M', u'Ꭼ'), + (0xAB7D, 'M', u'Ꭽ'), + (0xAB7E, 'M', u'Ꭾ'), + (0xAB7F, 'M', u'Ꭿ'), + (0xAB80, 'M', u'Ꮀ'), + (0xAB81, 'M', u'Ꮁ'), + (0xAB82, 'M', u'Ꮂ'), + (0xAB83, 'M', u'Ꮃ'), + (0xAB84, 'M', u'Ꮄ'), + (0xAB85, 'M', u'Ꮅ'), + (0xAB86, 'M', u'Ꮆ'), + (0xAB87, 'M', u'Ꮇ'), + (0xAB88, 'M', u'Ꮈ'), + (0xAB89, 'M', u'Ꮉ'), + (0xAB8A, 'M', u'Ꮊ'), + (0xAB8B, 'M', u'Ꮋ'), + (0xAB8C, 'M', u'Ꮌ'), + (0xAB8D, 'M', u'Ꮍ'), + (0xAB8E, 'M', u'Ꮎ'), + (0xAB8F, 'M', u'Ꮏ'), + (0xAB90, 'M', u'Ꮐ'), + (0xAB91, 'M', u'Ꮑ'), + (0xAB92, 'M', u'Ꮒ'), + (0xAB93, 'M', u'Ꮓ'), + (0xAB94, 'M', u'Ꮔ'), + (0xAB95, 'M', u'Ꮕ'), + (0xAB96, 'M', u'Ꮖ'), + (0xAB97, 'M', u'Ꮗ'), + (0xAB98, 'M', u'Ꮘ'), + (0xAB99, 'M', u'Ꮙ'), + (0xAB9A, 'M', u'Ꮚ'), + (0xAB9B, 'M', u'Ꮛ'), + (0xAB9C, 'M', u'Ꮜ'), + (0xAB9D, 'M', u'Ꮝ'), + (0xAB9E, 'M', u'Ꮞ'), + (0xAB9F, 'M', u'Ꮟ'), + (0xABA0, 'M', u'Ꮠ'), + (0xABA1, 'M', u'Ꮡ'), + (0xABA2, 'M', u'Ꮢ'), + (0xABA3, 'M', u'Ꮣ'), + (0xABA4, 'M', u'Ꮤ'), + (0xABA5, 'M', u'Ꮥ'), + (0xABA6, 'M', u'Ꮦ'), + (0xABA7, 'M', u'Ꮧ'), + (0xABA8, 'M', u'Ꮨ'), + (0xABA9, 'M', u'Ꮩ'), + (0xABAA, 'M', u'Ꮪ'), + (0xABAB, 'M', u'Ꮫ'), + (0xABAC, 'M', u'Ꮬ'), + (0xABAD, 'M', u'Ꮭ'), + (0xABAE, 'M', u'Ꮮ'), + (0xABAF, 'M', u'Ꮯ'), + (0xABB0, 'M', u'Ꮰ'), + (0xABB1, 'M', u'Ꮱ'), + (0xABB2, 'M', u'Ꮲ'), + (0xABB3, 'M', u'Ꮳ'), + (0xABB4, 'M', u'Ꮴ'), + (0xABB5, 'M', u'Ꮵ'), + (0xABB6, 'M', u'Ꮶ'), + (0xABB7, 'M', u'Ꮷ'), + (0xABB8, 'M', u'Ꮸ'), + (0xABB9, 'M', u'Ꮹ'), + (0xABBA, 'M', u'Ꮺ'), + (0xABBB, 'M', u'Ꮻ'), + (0xABBC, 'M', u'Ꮼ'), + (0xABBD, 'M', u'Ꮽ'), + (0xABBE, 'M', u'Ꮾ'), + (0xABBF, 'M', u'Ꮿ'), + (0xABC0, 'V'), + (0xABEE, 'X'), + (0xABF0, 'V'), + (0xABFA, 'X'), + (0xAC00, 'V'), + (0xD7A4, 'X'), + (0xD7B0, 'V'), + (0xD7C7, 'X'), + (0xD7CB, 'V'), + (0xD7FC, 'X'), + (0xF900, 'M', u'豈'), + (0xF901, 'M', u'更'), + (0xF902, 'M', u'車'), + (0xF903, 'M', u'賈'), + (0xF904, 'M', u'滑'), + (0xF905, 'M', u'串'), + (0xF906, 'M', u'句'), + (0xF907, 'M', u'龜'), + (0xF909, 'M', u'契'), + (0xF90A, 'M', u'金'), + (0xF90B, 'M', u'喇'), + (0xF90C, 'M', u'奈'), + (0xF90D, 'M', u'懶'), + (0xF90E, 'M', u'癩'), + (0xF90F, 'M', u'羅'), + (0xF910, 'M', u'蘿'), + (0xF911, 'M', u'螺'), + (0xF912, 'M', u'裸'), + (0xF913, 'M', u'邏'), + (0xF914, 'M', u'樂'), + (0xF915, 'M', u'洛'), + ] + +def _seg_39(): + return [ + (0xF916, 'M', u'烙'), + (0xF917, 'M', u'珞'), + (0xF918, 'M', u'落'), + (0xF919, 'M', u'酪'), + (0xF91A, 'M', u'駱'), + (0xF91B, 'M', u'亂'), + (0xF91C, 'M', u'卵'), + (0xF91D, 'M', u'欄'), + (0xF91E, 'M', u'爛'), + (0xF91F, 'M', u'蘭'), + (0xF920, 'M', u'鸞'), + (0xF921, 'M', u'嵐'), + (0xF922, 'M', u'濫'), + (0xF923, 'M', u'藍'), + (0xF924, 'M', u'襤'), + (0xF925, 'M', u'拉'), + (0xF926, 'M', u'臘'), + (0xF927, 'M', u'蠟'), + (0xF928, 'M', u'廊'), + (0xF929, 'M', u'朗'), + (0xF92A, 'M', u'浪'), + (0xF92B, 'M', u'狼'), + (0xF92C, 'M', u'郎'), + (0xF92D, 'M', u'來'), + (0xF92E, 'M', u'冷'), + (0xF92F, 'M', u'勞'), + (0xF930, 'M', u'擄'), + (0xF931, 'M', u'櫓'), + (0xF932, 'M', u'爐'), + (0xF933, 'M', u'盧'), + (0xF934, 'M', u'老'), + (0xF935, 'M', u'蘆'), + (0xF936, 'M', u'虜'), + (0xF937, 'M', u'路'), + (0xF938, 'M', u'露'), + (0xF939, 'M', u'魯'), + (0xF93A, 'M', u'鷺'), + (0xF93B, 'M', u'碌'), + (0xF93C, 'M', u'祿'), + (0xF93D, 'M', u'綠'), + (0xF93E, 'M', u'菉'), + (0xF93F, 'M', u'錄'), + (0xF940, 'M', u'鹿'), + (0xF941, 'M', u'論'), + (0xF942, 'M', u'壟'), + (0xF943, 'M', u'弄'), + (0xF944, 'M', u'籠'), + (0xF945, 'M', u'聾'), + (0xF946, 'M', u'牢'), + (0xF947, 'M', u'磊'), + (0xF948, 'M', u'賂'), + (0xF949, 'M', u'雷'), + (0xF94A, 'M', u'壘'), + (0xF94B, 'M', u'屢'), + (0xF94C, 'M', u'樓'), + (0xF94D, 'M', u'淚'), + (0xF94E, 'M', u'漏'), + (0xF94F, 'M', u'累'), + (0xF950, 'M', u'縷'), + (0xF951, 'M', u'陋'), + (0xF952, 'M', u'勒'), + (0xF953, 'M', u'肋'), + (0xF954, 'M', u'凜'), + (0xF955, 'M', u'凌'), + (0xF956, 'M', u'稜'), + (0xF957, 'M', u'綾'), + (0xF958, 'M', u'菱'), + (0xF959, 'M', u'陵'), + (0xF95A, 'M', u'讀'), + (0xF95B, 'M', u'拏'), + (0xF95C, 'M', u'樂'), + (0xF95D, 'M', u'諾'), + (0xF95E, 'M', u'丹'), + (0xF95F, 'M', u'寧'), + (0xF960, 'M', u'怒'), + (0xF961, 'M', u'率'), + (0xF962, 'M', u'異'), + (0xF963, 'M', u'北'), + (0xF964, 'M', u'磻'), + (0xF965, 'M', u'便'), + (0xF966, 'M', u'復'), + (0xF967, 'M', u'不'), + (0xF968, 'M', u'泌'), + (0xF969, 'M', u'數'), + (0xF96A, 'M', u'索'), + (0xF96B, 'M', u'參'), + (0xF96C, 'M', u'塞'), + (0xF96D, 'M', u'省'), + (0xF96E, 'M', u'葉'), + (0xF96F, 'M', u'說'), + (0xF970, 'M', u'殺'), + (0xF971, 'M', u'辰'), + (0xF972, 'M', u'沈'), + (0xF973, 'M', u'拾'), + (0xF974, 'M', u'若'), + (0xF975, 'M', u'掠'), + (0xF976, 'M', u'略'), + (0xF977, 'M', u'亮'), + (0xF978, 'M', u'兩'), + (0xF979, 'M', u'凉'), + ] + +def _seg_40(): + return [ + (0xF97A, 'M', u'梁'), + (0xF97B, 'M', u'糧'), + (0xF97C, 'M', u'良'), + (0xF97D, 'M', u'諒'), + (0xF97E, 'M', u'量'), + (0xF97F, 'M', u'勵'), + (0xF980, 'M', u'呂'), + (0xF981, 'M', u'女'), + (0xF982, 'M', u'廬'), + (0xF983, 'M', u'旅'), + (0xF984, 'M', u'濾'), + (0xF985, 'M', u'礪'), + (0xF986, 'M', u'閭'), + (0xF987, 'M', u'驪'), + (0xF988, 'M', u'麗'), + (0xF989, 'M', u'黎'), + (0xF98A, 'M', u'力'), + (0xF98B, 'M', u'曆'), + (0xF98C, 'M', u'歷'), + (0xF98D, 'M', u'轢'), + (0xF98E, 'M', u'年'), + (0xF98F, 'M', u'憐'), + (0xF990, 'M', u'戀'), + (0xF991, 'M', u'撚'), + (0xF992, 'M', u'漣'), + (0xF993, 'M', u'煉'), + (0xF994, 'M', u'璉'), + (0xF995, 'M', u'秊'), + (0xF996, 'M', u'練'), + (0xF997, 'M', u'聯'), + (0xF998, 'M', u'輦'), + (0xF999, 'M', u'蓮'), + (0xF99A, 'M', u'連'), + (0xF99B, 'M', u'鍊'), + (0xF99C, 'M', u'列'), + (0xF99D, 'M', u'劣'), + (0xF99E, 'M', u'咽'), + (0xF99F, 'M', u'烈'), + (0xF9A0, 'M', u'裂'), + (0xF9A1, 'M', u'說'), + (0xF9A2, 'M', u'廉'), + (0xF9A3, 'M', u'念'), + (0xF9A4, 'M', u'捻'), + (0xF9A5, 'M', u'殮'), + (0xF9A6, 'M', u'簾'), + (0xF9A7, 'M', u'獵'), + (0xF9A8, 'M', u'令'), + (0xF9A9, 'M', u'囹'), + (0xF9AA, 'M', u'寧'), + (0xF9AB, 'M', u'嶺'), + (0xF9AC, 'M', u'怜'), + (0xF9AD, 'M', u'玲'), + (0xF9AE, 'M', u'瑩'), + (0xF9AF, 'M', u'羚'), + (0xF9B0, 'M', u'聆'), + (0xF9B1, 'M', u'鈴'), + (0xF9B2, 'M', u'零'), + (0xF9B3, 'M', u'靈'), + (0xF9B4, 'M', u'領'), + (0xF9B5, 'M', u'例'), + (0xF9B6, 'M', u'禮'), + (0xF9B7, 'M', u'醴'), + (0xF9B8, 'M', u'隸'), + (0xF9B9, 'M', u'惡'), + (0xF9BA, 'M', u'了'), + (0xF9BB, 'M', u'僚'), + (0xF9BC, 'M', u'寮'), + (0xF9BD, 'M', u'尿'), + (0xF9BE, 'M', u'料'), + (0xF9BF, 'M', u'樂'), + (0xF9C0, 'M', u'燎'), + (0xF9C1, 'M', u'療'), + (0xF9C2, 'M', u'蓼'), + (0xF9C3, 'M', u'遼'), + (0xF9C4, 'M', u'龍'), + (0xF9C5, 'M', u'暈'), + (0xF9C6, 'M', u'阮'), + (0xF9C7, 'M', u'劉'), + (0xF9C8, 'M', u'杻'), + (0xF9C9, 'M', u'柳'), + (0xF9CA, 'M', u'流'), + (0xF9CB, 'M', u'溜'), + (0xF9CC, 'M', u'琉'), + (0xF9CD, 'M', u'留'), + (0xF9CE, 'M', u'硫'), + (0xF9CF, 'M', u'紐'), + (0xF9D0, 'M', u'類'), + (0xF9D1, 'M', u'六'), + (0xF9D2, 'M', u'戮'), + (0xF9D3, 'M', u'陸'), + (0xF9D4, 'M', u'倫'), + (0xF9D5, 'M', u'崙'), + (0xF9D6, 'M', u'淪'), + (0xF9D7, 'M', u'輪'), + (0xF9D8, 'M', u'律'), + (0xF9D9, 'M', u'慄'), + (0xF9DA, 'M', u'栗'), + (0xF9DB, 'M', u'率'), + (0xF9DC, 'M', u'隆'), + (0xF9DD, 'M', u'利'), + ] + +def _seg_41(): + return [ + (0xF9DE, 'M', u'吏'), + (0xF9DF, 'M', u'履'), + (0xF9E0, 'M', u'易'), + (0xF9E1, 'M', u'李'), + (0xF9E2, 'M', u'梨'), + (0xF9E3, 'M', u'泥'), + (0xF9E4, 'M', u'理'), + (0xF9E5, 'M', u'痢'), + (0xF9E6, 'M', u'罹'), + (0xF9E7, 'M', u'裏'), + (0xF9E8, 'M', u'裡'), + (0xF9E9, 'M', u'里'), + (0xF9EA, 'M', u'離'), + (0xF9EB, 'M', u'匿'), + (0xF9EC, 'M', u'溺'), + (0xF9ED, 'M', u'吝'), + (0xF9EE, 'M', u'燐'), + (0xF9EF, 'M', u'璘'), + (0xF9F0, 'M', u'藺'), + (0xF9F1, 'M', u'隣'), + (0xF9F2, 'M', u'鱗'), + (0xF9F3, 'M', u'麟'), + (0xF9F4, 'M', u'林'), + (0xF9F5, 'M', u'淋'), + (0xF9F6, 'M', u'臨'), + (0xF9F7, 'M', u'立'), + (0xF9F8, 'M', u'笠'), + (0xF9F9, 'M', u'粒'), + (0xF9FA, 'M', u'狀'), + (0xF9FB, 'M', u'炙'), + (0xF9FC, 'M', u'識'), + (0xF9FD, 'M', u'什'), + (0xF9FE, 'M', u'茶'), + (0xF9FF, 'M', u'刺'), + (0xFA00, 'M', u'切'), + (0xFA01, 'M', u'度'), + (0xFA02, 'M', u'拓'), + (0xFA03, 'M', u'糖'), + (0xFA04, 'M', u'宅'), + (0xFA05, 'M', u'洞'), + (0xFA06, 'M', u'暴'), + (0xFA07, 'M', u'輻'), + (0xFA08, 'M', u'行'), + (0xFA09, 'M', u'降'), + (0xFA0A, 'M', u'見'), + (0xFA0B, 'M', u'廓'), + (0xFA0C, 'M', u'兀'), + (0xFA0D, 'M', u'嗀'), + (0xFA0E, 'V'), + (0xFA10, 'M', u'塚'), + (0xFA11, 'V'), + (0xFA12, 'M', u'晴'), + (0xFA13, 'V'), + (0xFA15, 'M', u'凞'), + (0xFA16, 'M', u'猪'), + (0xFA17, 'M', u'益'), + (0xFA18, 'M', u'礼'), + (0xFA19, 'M', u'神'), + (0xFA1A, 'M', u'祥'), + (0xFA1B, 'M', u'福'), + (0xFA1C, 'M', u'靖'), + (0xFA1D, 'M', u'精'), + (0xFA1E, 'M', u'羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', u'蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', u'諸'), + (0xFA23, 'V'), + (0xFA25, 'M', u'逸'), + (0xFA26, 'M', u'都'), + (0xFA27, 'V'), + (0xFA2A, 'M', u'飯'), + (0xFA2B, 'M', u'飼'), + (0xFA2C, 'M', u'館'), + (0xFA2D, 'M', u'鶴'), + (0xFA2E, 'M', u'郞'), + (0xFA2F, 'M', u'隷'), + (0xFA30, 'M', u'侮'), + (0xFA31, 'M', u'僧'), + (0xFA32, 'M', u'免'), + (0xFA33, 'M', u'勉'), + (0xFA34, 'M', u'勤'), + (0xFA35, 'M', u'卑'), + (0xFA36, 'M', u'喝'), + (0xFA37, 'M', u'嘆'), + (0xFA38, 'M', u'器'), + (0xFA39, 'M', u'塀'), + (0xFA3A, 'M', u'墨'), + (0xFA3B, 'M', u'層'), + (0xFA3C, 'M', u'屮'), + (0xFA3D, 'M', u'悔'), + (0xFA3E, 'M', u'慨'), + (0xFA3F, 'M', u'憎'), + (0xFA40, 'M', u'懲'), + (0xFA41, 'M', u'敏'), + (0xFA42, 'M', u'既'), + (0xFA43, 'M', u'暑'), + (0xFA44, 'M', u'梅'), + (0xFA45, 'M', u'海'), + (0xFA46, 'M', u'渚'), + ] + +def _seg_42(): + return [ + (0xFA47, 'M', u'漢'), + (0xFA48, 'M', u'煮'), + (0xFA49, 'M', u'爫'), + (0xFA4A, 'M', u'琢'), + (0xFA4B, 'M', u'碑'), + (0xFA4C, 'M', u'社'), + (0xFA4D, 'M', u'祉'), + (0xFA4E, 'M', u'祈'), + (0xFA4F, 'M', u'祐'), + (0xFA50, 'M', u'祖'), + (0xFA51, 'M', u'祝'), + (0xFA52, 'M', u'禍'), + (0xFA53, 'M', u'禎'), + (0xFA54, 'M', u'穀'), + (0xFA55, 'M', u'突'), + (0xFA56, 'M', u'節'), + (0xFA57, 'M', u'練'), + (0xFA58, 'M', u'縉'), + (0xFA59, 'M', u'繁'), + (0xFA5A, 'M', u'署'), + (0xFA5B, 'M', u'者'), + (0xFA5C, 'M', u'臭'), + (0xFA5D, 'M', u'艹'), + (0xFA5F, 'M', u'著'), + (0xFA60, 'M', u'褐'), + (0xFA61, 'M', u'視'), + (0xFA62, 'M', u'謁'), + (0xFA63, 'M', u'謹'), + (0xFA64, 'M', u'賓'), + (0xFA65, 'M', u'贈'), + (0xFA66, 'M', u'辶'), + (0xFA67, 'M', u'逸'), + (0xFA68, 'M', u'難'), + (0xFA69, 'M', u'響'), + (0xFA6A, 'M', u'頻'), + (0xFA6B, 'M', u'恵'), + (0xFA6C, 'M', u'𤋮'), + (0xFA6D, 'M', u'舘'), + (0xFA6E, 'X'), + (0xFA70, 'M', u'並'), + (0xFA71, 'M', u'况'), + (0xFA72, 'M', u'全'), + (0xFA73, 'M', u'侀'), + (0xFA74, 'M', u'充'), + (0xFA75, 'M', u'冀'), + (0xFA76, 'M', u'勇'), + (0xFA77, 'M', u'勺'), + (0xFA78, 'M', u'喝'), + (0xFA79, 'M', u'啕'), + (0xFA7A, 'M', u'喙'), + (0xFA7B, 'M', u'嗢'), + (0xFA7C, 'M', u'塚'), + (0xFA7D, 'M', u'墳'), + (0xFA7E, 'M', u'奄'), + (0xFA7F, 'M', u'奔'), + (0xFA80, 'M', u'婢'), + (0xFA81, 'M', u'嬨'), + (0xFA82, 'M', u'廒'), + (0xFA83, 'M', u'廙'), + (0xFA84, 'M', u'彩'), + (0xFA85, 'M', u'徭'), + (0xFA86, 'M', u'惘'), + (0xFA87, 'M', u'慎'), + (0xFA88, 'M', u'愈'), + (0xFA89, 'M', u'憎'), + (0xFA8A, 'M', u'慠'), + (0xFA8B, 'M', u'懲'), + (0xFA8C, 'M', u'戴'), + (0xFA8D, 'M', u'揄'), + (0xFA8E, 'M', u'搜'), + (0xFA8F, 'M', u'摒'), + (0xFA90, 'M', u'敖'), + (0xFA91, 'M', u'晴'), + (0xFA92, 'M', u'朗'), + (0xFA93, 'M', u'望'), + (0xFA94, 'M', u'杖'), + (0xFA95, 'M', u'歹'), + (0xFA96, 'M', u'殺'), + (0xFA97, 'M', u'流'), + (0xFA98, 'M', u'滛'), + (0xFA99, 'M', u'滋'), + (0xFA9A, 'M', u'漢'), + (0xFA9B, 'M', u'瀞'), + (0xFA9C, 'M', u'煮'), + (0xFA9D, 'M', u'瞧'), + (0xFA9E, 'M', u'爵'), + (0xFA9F, 'M', u'犯'), + (0xFAA0, 'M', u'猪'), + (0xFAA1, 'M', u'瑱'), + (0xFAA2, 'M', u'甆'), + (0xFAA3, 'M', u'画'), + (0xFAA4, 'M', u'瘝'), + (0xFAA5, 'M', u'瘟'), + (0xFAA6, 'M', u'益'), + (0xFAA7, 'M', u'盛'), + (0xFAA8, 'M', u'直'), + (0xFAA9, 'M', u'睊'), + (0xFAAA, 'M', u'着'), + (0xFAAB, 'M', u'磌'), + (0xFAAC, 'M', u'窱'), + ] + +def _seg_43(): + return [ + (0xFAAD, 'M', u'節'), + (0xFAAE, 'M', u'类'), + (0xFAAF, 'M', u'絛'), + (0xFAB0, 'M', u'練'), + (0xFAB1, 'M', u'缾'), + (0xFAB2, 'M', u'者'), + (0xFAB3, 'M', u'荒'), + (0xFAB4, 'M', u'華'), + (0xFAB5, 'M', u'蝹'), + (0xFAB6, 'M', u'襁'), + (0xFAB7, 'M', u'覆'), + (0xFAB8, 'M', u'視'), + (0xFAB9, 'M', u'調'), + (0xFABA, 'M', u'諸'), + (0xFABB, 'M', u'請'), + (0xFABC, 'M', u'謁'), + (0xFABD, 'M', u'諾'), + (0xFABE, 'M', u'諭'), + (0xFABF, 'M', u'謹'), + (0xFAC0, 'M', u'變'), + (0xFAC1, 'M', u'贈'), + (0xFAC2, 'M', u'輸'), + (0xFAC3, 'M', u'遲'), + (0xFAC4, 'M', u'醙'), + (0xFAC5, 'M', u'鉶'), + (0xFAC6, 'M', u'陼'), + (0xFAC7, 'M', u'難'), + (0xFAC8, 'M', u'靖'), + (0xFAC9, 'M', u'韛'), + (0xFACA, 'M', u'響'), + (0xFACB, 'M', u'頋'), + (0xFACC, 'M', u'頻'), + (0xFACD, 'M', u'鬒'), + (0xFACE, 'M', u'龜'), + (0xFACF, 'M', u'𢡊'), + (0xFAD0, 'M', u'𢡄'), + (0xFAD1, 'M', u'𣏕'), + (0xFAD2, 'M', u'㮝'), + (0xFAD3, 'M', u'䀘'), + (0xFAD4, 'M', u'䀹'), + (0xFAD5, 'M', u'𥉉'), + (0xFAD6, 'M', u'𥳐'), + (0xFAD7, 'M', u'𧻓'), + (0xFAD8, 'M', u'齃'), + (0xFAD9, 'M', u'龎'), + (0xFADA, 'X'), + (0xFB00, 'M', u'ff'), + (0xFB01, 'M', u'fi'), + (0xFB02, 'M', u'fl'), + (0xFB03, 'M', u'ffi'), + (0xFB04, 'M', u'ffl'), + (0xFB05, 'M', u'st'), + (0xFB07, 'X'), + (0xFB13, 'M', u'մն'), + (0xFB14, 'M', u'մե'), + (0xFB15, 'M', u'մի'), + (0xFB16, 'M', u'վն'), + (0xFB17, 'M', u'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', u'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', u'ײַ'), + (0xFB20, 'M', u'ע'), + (0xFB21, 'M', u'א'), + (0xFB22, 'M', u'ד'), + (0xFB23, 'M', u'ה'), + (0xFB24, 'M', u'כ'), + (0xFB25, 'M', u'ל'), + (0xFB26, 'M', u'ם'), + (0xFB27, 'M', u'ר'), + (0xFB28, 'M', u'ת'), + (0xFB29, '3', u'+'), + (0xFB2A, 'M', u'שׁ'), + (0xFB2B, 'M', u'שׂ'), + (0xFB2C, 'M', u'שּׁ'), + (0xFB2D, 'M', u'שּׂ'), + (0xFB2E, 'M', u'אַ'), + (0xFB2F, 'M', u'אָ'), + (0xFB30, 'M', u'אּ'), + (0xFB31, 'M', u'בּ'), + (0xFB32, 'M', u'גּ'), + (0xFB33, 'M', u'דּ'), + (0xFB34, 'M', u'הּ'), + (0xFB35, 'M', u'וּ'), + (0xFB36, 'M', u'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', u'טּ'), + (0xFB39, 'M', u'יּ'), + (0xFB3A, 'M', u'ךּ'), + (0xFB3B, 'M', u'כּ'), + (0xFB3C, 'M', u'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', u'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', u'נּ'), + (0xFB41, 'M', u'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', u'ףּ'), + (0xFB44, 'M', u'פּ'), + (0xFB45, 'X'), + ] + +def _seg_44(): + return [ + (0xFB46, 'M', u'צּ'), + (0xFB47, 'M', u'קּ'), + (0xFB48, 'M', u'רּ'), + (0xFB49, 'M', u'שּ'), + (0xFB4A, 'M', u'תּ'), + (0xFB4B, 'M', u'וֹ'), + (0xFB4C, 'M', u'בֿ'), + (0xFB4D, 'M', u'כֿ'), + (0xFB4E, 'M', u'פֿ'), + (0xFB4F, 'M', u'אל'), + (0xFB50, 'M', u'ٱ'), + (0xFB52, 'M', u'ٻ'), + (0xFB56, 'M', u'پ'), + (0xFB5A, 'M', u'ڀ'), + (0xFB5E, 'M', u'ٺ'), + (0xFB62, 'M', u'ٿ'), + (0xFB66, 'M', u'ٹ'), + (0xFB6A, 'M', u'ڤ'), + (0xFB6E, 'M', u'ڦ'), + (0xFB72, 'M', u'ڄ'), + (0xFB76, 'M', u'ڃ'), + (0xFB7A, 'M', u'چ'), + (0xFB7E, 'M', u'ڇ'), + (0xFB82, 'M', u'ڍ'), + (0xFB84, 'M', u'ڌ'), + (0xFB86, 'M', u'ڎ'), + (0xFB88, 'M', u'ڈ'), + (0xFB8A, 'M', u'ژ'), + (0xFB8C, 'M', u'ڑ'), + (0xFB8E, 'M', u'ک'), + (0xFB92, 'M', u'گ'), + (0xFB96, 'M', u'ڳ'), + (0xFB9A, 'M', u'ڱ'), + (0xFB9E, 'M', u'ں'), + (0xFBA0, 'M', u'ڻ'), + (0xFBA4, 'M', u'ۀ'), + (0xFBA6, 'M', u'ہ'), + (0xFBAA, 'M', u'ھ'), + (0xFBAE, 'M', u'ے'), + (0xFBB0, 'M', u'ۓ'), + (0xFBB2, 'V'), + (0xFBC2, 'X'), + (0xFBD3, 'M', u'ڭ'), + (0xFBD7, 'M', u'ۇ'), + (0xFBD9, 'M', u'ۆ'), + (0xFBDB, 'M', u'ۈ'), + (0xFBDD, 'M', u'ۇٴ'), + (0xFBDE, 'M', u'ۋ'), + (0xFBE0, 'M', u'ۅ'), + (0xFBE2, 'M', u'ۉ'), + (0xFBE4, 'M', u'ې'), + (0xFBE8, 'M', u'ى'), + (0xFBEA, 'M', u'ئا'), + (0xFBEC, 'M', u'ئە'), + (0xFBEE, 'M', u'ئو'), + (0xFBF0, 'M', u'ئۇ'), + (0xFBF2, 'M', u'ئۆ'), + (0xFBF4, 'M', u'ئۈ'), + (0xFBF6, 'M', u'ئې'), + (0xFBF9, 'M', u'ئى'), + (0xFBFC, 'M', u'ی'), + (0xFC00, 'M', u'ئج'), + (0xFC01, 'M', u'ئح'), + (0xFC02, 'M', u'ئم'), + (0xFC03, 'M', u'ئى'), + (0xFC04, 'M', u'ئي'), + (0xFC05, 'M', u'بج'), + (0xFC06, 'M', u'بح'), + (0xFC07, 'M', u'بخ'), + (0xFC08, 'M', u'بم'), + (0xFC09, 'M', u'بى'), + (0xFC0A, 'M', u'بي'), + (0xFC0B, 'M', u'تج'), + (0xFC0C, 'M', u'تح'), + (0xFC0D, 'M', u'تخ'), + (0xFC0E, 'M', u'تم'), + (0xFC0F, 'M', u'تى'), + (0xFC10, 'M', u'تي'), + (0xFC11, 'M', u'ثج'), + (0xFC12, 'M', u'ثم'), + (0xFC13, 'M', u'ثى'), + (0xFC14, 'M', u'ثي'), + (0xFC15, 'M', u'جح'), + (0xFC16, 'M', u'جم'), + (0xFC17, 'M', u'حج'), + (0xFC18, 'M', u'حم'), + (0xFC19, 'M', u'خج'), + (0xFC1A, 'M', u'خح'), + (0xFC1B, 'M', u'خم'), + (0xFC1C, 'M', u'سج'), + (0xFC1D, 'M', u'سح'), + (0xFC1E, 'M', u'سخ'), + (0xFC1F, 'M', u'سم'), + (0xFC20, 'M', u'صح'), + (0xFC21, 'M', u'صم'), + (0xFC22, 'M', u'ضج'), + (0xFC23, 'M', u'ضح'), + (0xFC24, 'M', u'ضخ'), + (0xFC25, 'M', u'ضم'), + (0xFC26, 'M', u'طح'), + ] + +def _seg_45(): + return [ + (0xFC27, 'M', u'طم'), + (0xFC28, 'M', u'ظم'), + (0xFC29, 'M', u'عج'), + (0xFC2A, 'M', u'عم'), + (0xFC2B, 'M', u'غج'), + (0xFC2C, 'M', u'غم'), + (0xFC2D, 'M', u'فج'), + (0xFC2E, 'M', u'فح'), + (0xFC2F, 'M', u'فخ'), + (0xFC30, 'M', u'فم'), + (0xFC31, 'M', u'فى'), + (0xFC32, 'M', u'في'), + (0xFC33, 'M', u'قح'), + (0xFC34, 'M', u'قم'), + (0xFC35, 'M', u'قى'), + (0xFC36, 'M', u'قي'), + (0xFC37, 'M', u'كا'), + (0xFC38, 'M', u'كج'), + (0xFC39, 'M', u'كح'), + (0xFC3A, 'M', u'كخ'), + (0xFC3B, 'M', u'كل'), + (0xFC3C, 'M', u'كم'), + (0xFC3D, 'M', u'كى'), + (0xFC3E, 'M', u'كي'), + (0xFC3F, 'M', u'لج'), + (0xFC40, 'M', u'لح'), + (0xFC41, 'M', u'لخ'), + (0xFC42, 'M', u'لم'), + (0xFC43, 'M', u'لى'), + (0xFC44, 'M', u'لي'), + (0xFC45, 'M', u'مج'), + (0xFC46, 'M', u'مح'), + (0xFC47, 'M', u'مخ'), + (0xFC48, 'M', u'مم'), + (0xFC49, 'M', u'مى'), + (0xFC4A, 'M', u'مي'), + (0xFC4B, 'M', u'نج'), + (0xFC4C, 'M', u'نح'), + (0xFC4D, 'M', u'نخ'), + (0xFC4E, 'M', u'نم'), + (0xFC4F, 'M', u'نى'), + (0xFC50, 'M', u'ني'), + (0xFC51, 'M', u'هج'), + (0xFC52, 'M', u'هم'), + (0xFC53, 'M', u'هى'), + (0xFC54, 'M', u'هي'), + (0xFC55, 'M', u'يج'), + (0xFC56, 'M', u'يح'), + (0xFC57, 'M', u'يخ'), + (0xFC58, 'M', u'يم'), + (0xFC59, 'M', u'يى'), + (0xFC5A, 'M', u'يي'), + (0xFC5B, 'M', u'ذٰ'), + (0xFC5C, 'M', u'رٰ'), + (0xFC5D, 'M', u'ىٰ'), + (0xFC5E, '3', u' ٌّ'), + (0xFC5F, '3', u' ٍّ'), + (0xFC60, '3', u' َّ'), + (0xFC61, '3', u' ُّ'), + (0xFC62, '3', u' ِّ'), + (0xFC63, '3', u' ّٰ'), + (0xFC64, 'M', u'ئر'), + (0xFC65, 'M', u'ئز'), + (0xFC66, 'M', u'ئم'), + (0xFC67, 'M', u'ئن'), + (0xFC68, 'M', u'ئى'), + (0xFC69, 'M', u'ئي'), + (0xFC6A, 'M', u'بر'), + (0xFC6B, 'M', u'بز'), + (0xFC6C, 'M', u'بم'), + (0xFC6D, 'M', u'بن'), + (0xFC6E, 'M', u'بى'), + (0xFC6F, 'M', u'بي'), + (0xFC70, 'M', u'تر'), + (0xFC71, 'M', u'تز'), + (0xFC72, 'M', u'تم'), + (0xFC73, 'M', u'تن'), + (0xFC74, 'M', u'تى'), + (0xFC75, 'M', u'تي'), + (0xFC76, 'M', u'ثر'), + (0xFC77, 'M', u'ثز'), + (0xFC78, 'M', u'ثم'), + (0xFC79, 'M', u'ثن'), + (0xFC7A, 'M', u'ثى'), + (0xFC7B, 'M', u'ثي'), + (0xFC7C, 'M', u'فى'), + (0xFC7D, 'M', u'في'), + (0xFC7E, 'M', u'قى'), + (0xFC7F, 'M', u'قي'), + (0xFC80, 'M', u'كا'), + (0xFC81, 'M', u'كل'), + (0xFC82, 'M', u'كم'), + (0xFC83, 'M', u'كى'), + (0xFC84, 'M', u'كي'), + (0xFC85, 'M', u'لم'), + (0xFC86, 'M', u'لى'), + (0xFC87, 'M', u'لي'), + (0xFC88, 'M', u'ما'), + (0xFC89, 'M', u'مم'), + (0xFC8A, 'M', u'نر'), + ] + +def _seg_46(): + return [ + (0xFC8B, 'M', u'نز'), + (0xFC8C, 'M', u'نم'), + (0xFC8D, 'M', u'نن'), + (0xFC8E, 'M', u'نى'), + (0xFC8F, 'M', u'ني'), + (0xFC90, 'M', u'ىٰ'), + (0xFC91, 'M', u'ير'), + (0xFC92, 'M', u'يز'), + (0xFC93, 'M', u'يم'), + (0xFC94, 'M', u'ين'), + (0xFC95, 'M', u'يى'), + (0xFC96, 'M', u'يي'), + (0xFC97, 'M', u'ئج'), + (0xFC98, 'M', u'ئح'), + (0xFC99, 'M', u'ئخ'), + (0xFC9A, 'M', u'ئم'), + (0xFC9B, 'M', u'ئه'), + (0xFC9C, 'M', u'بج'), + (0xFC9D, 'M', u'بح'), + (0xFC9E, 'M', u'بخ'), + (0xFC9F, 'M', u'بم'), + (0xFCA0, 'M', u'به'), + (0xFCA1, 'M', u'تج'), + (0xFCA2, 'M', u'تح'), + (0xFCA3, 'M', u'تخ'), + (0xFCA4, 'M', u'تم'), + (0xFCA5, 'M', u'ته'), + (0xFCA6, 'M', u'ثم'), + (0xFCA7, 'M', u'جح'), + (0xFCA8, 'M', u'جم'), + (0xFCA9, 'M', u'حج'), + (0xFCAA, 'M', u'حم'), + (0xFCAB, 'M', u'خج'), + (0xFCAC, 'M', u'خم'), + (0xFCAD, 'M', u'سج'), + (0xFCAE, 'M', u'سح'), + (0xFCAF, 'M', u'سخ'), + (0xFCB0, 'M', u'سم'), + (0xFCB1, 'M', u'صح'), + (0xFCB2, 'M', u'صخ'), + (0xFCB3, 'M', u'صم'), + (0xFCB4, 'M', u'ضج'), + (0xFCB5, 'M', u'ضح'), + (0xFCB6, 'M', u'ضخ'), + (0xFCB7, 'M', u'ضم'), + (0xFCB8, 'M', u'طح'), + (0xFCB9, 'M', u'ظم'), + (0xFCBA, 'M', u'عج'), + (0xFCBB, 'M', u'عم'), + (0xFCBC, 'M', u'غج'), + (0xFCBD, 'M', u'غم'), + (0xFCBE, 'M', u'فج'), + (0xFCBF, 'M', u'فح'), + (0xFCC0, 'M', u'فخ'), + (0xFCC1, 'M', u'فم'), + (0xFCC2, 'M', u'قح'), + (0xFCC3, 'M', u'قم'), + (0xFCC4, 'M', u'كج'), + (0xFCC5, 'M', u'كح'), + (0xFCC6, 'M', u'كخ'), + (0xFCC7, 'M', u'كل'), + (0xFCC8, 'M', u'كم'), + (0xFCC9, 'M', u'لج'), + (0xFCCA, 'M', u'لح'), + (0xFCCB, 'M', u'لخ'), + (0xFCCC, 'M', u'لم'), + (0xFCCD, 'M', u'له'), + (0xFCCE, 'M', u'مج'), + (0xFCCF, 'M', u'مح'), + (0xFCD0, 'M', u'مخ'), + (0xFCD1, 'M', u'مم'), + (0xFCD2, 'M', u'نج'), + (0xFCD3, 'M', u'نح'), + (0xFCD4, 'M', u'نخ'), + (0xFCD5, 'M', u'نم'), + (0xFCD6, 'M', u'نه'), + (0xFCD7, 'M', u'هج'), + (0xFCD8, 'M', u'هم'), + (0xFCD9, 'M', u'هٰ'), + (0xFCDA, 'M', u'يج'), + (0xFCDB, 'M', u'يح'), + (0xFCDC, 'M', u'يخ'), + (0xFCDD, 'M', u'يم'), + (0xFCDE, 'M', u'يه'), + (0xFCDF, 'M', u'ئم'), + (0xFCE0, 'M', u'ئه'), + (0xFCE1, 'M', u'بم'), + (0xFCE2, 'M', u'به'), + (0xFCE3, 'M', u'تم'), + (0xFCE4, 'M', u'ته'), + (0xFCE5, 'M', u'ثم'), + (0xFCE6, 'M', u'ثه'), + (0xFCE7, 'M', u'سم'), + (0xFCE8, 'M', u'سه'), + (0xFCE9, 'M', u'شم'), + (0xFCEA, 'M', u'شه'), + (0xFCEB, 'M', u'كل'), + (0xFCEC, 'M', u'كم'), + (0xFCED, 'M', u'لم'), + (0xFCEE, 'M', u'نم'), + ] + +def _seg_47(): + return [ + (0xFCEF, 'M', u'نه'), + (0xFCF0, 'M', u'يم'), + (0xFCF1, 'M', u'يه'), + (0xFCF2, 'M', u'ـَّ'), + (0xFCF3, 'M', u'ـُّ'), + (0xFCF4, 'M', u'ـِّ'), + (0xFCF5, 'M', u'طى'), + (0xFCF6, 'M', u'طي'), + (0xFCF7, 'M', u'عى'), + (0xFCF8, 'M', u'عي'), + (0xFCF9, 'M', u'غى'), + (0xFCFA, 'M', u'غي'), + (0xFCFB, 'M', u'سى'), + (0xFCFC, 'M', u'سي'), + (0xFCFD, 'M', u'شى'), + (0xFCFE, 'M', u'شي'), + (0xFCFF, 'M', u'حى'), + (0xFD00, 'M', u'حي'), + (0xFD01, 'M', u'جى'), + (0xFD02, 'M', u'جي'), + (0xFD03, 'M', u'خى'), + (0xFD04, 'M', u'خي'), + (0xFD05, 'M', u'صى'), + (0xFD06, 'M', u'صي'), + (0xFD07, 'M', u'ضى'), + (0xFD08, 'M', u'ضي'), + (0xFD09, 'M', u'شج'), + (0xFD0A, 'M', u'شح'), + (0xFD0B, 'M', u'شخ'), + (0xFD0C, 'M', u'شم'), + (0xFD0D, 'M', u'شر'), + (0xFD0E, 'M', u'سر'), + (0xFD0F, 'M', u'صر'), + (0xFD10, 'M', u'ضر'), + (0xFD11, 'M', u'طى'), + (0xFD12, 'M', u'طي'), + (0xFD13, 'M', u'عى'), + (0xFD14, 'M', u'عي'), + (0xFD15, 'M', u'غى'), + (0xFD16, 'M', u'غي'), + (0xFD17, 'M', u'سى'), + (0xFD18, 'M', u'سي'), + (0xFD19, 'M', u'شى'), + (0xFD1A, 'M', u'شي'), + (0xFD1B, 'M', u'حى'), + (0xFD1C, 'M', u'حي'), + (0xFD1D, 'M', u'جى'), + (0xFD1E, 'M', u'جي'), + (0xFD1F, 'M', u'خى'), + (0xFD20, 'M', u'خي'), + (0xFD21, 'M', u'صى'), + (0xFD22, 'M', u'صي'), + (0xFD23, 'M', u'ضى'), + (0xFD24, 'M', u'ضي'), + (0xFD25, 'M', u'شج'), + (0xFD26, 'M', u'شح'), + (0xFD27, 'M', u'شخ'), + (0xFD28, 'M', u'شم'), + (0xFD29, 'M', u'شر'), + (0xFD2A, 'M', u'سر'), + (0xFD2B, 'M', u'صر'), + (0xFD2C, 'M', u'ضر'), + (0xFD2D, 'M', u'شج'), + (0xFD2E, 'M', u'شح'), + (0xFD2F, 'M', u'شخ'), + (0xFD30, 'M', u'شم'), + (0xFD31, 'M', u'سه'), + (0xFD32, 'M', u'شه'), + (0xFD33, 'M', u'طم'), + (0xFD34, 'M', u'سج'), + (0xFD35, 'M', u'سح'), + (0xFD36, 'M', u'سخ'), + (0xFD37, 'M', u'شج'), + (0xFD38, 'M', u'شح'), + (0xFD39, 'M', u'شخ'), + (0xFD3A, 'M', u'طم'), + (0xFD3B, 'M', u'ظم'), + (0xFD3C, 'M', u'اً'), + (0xFD3E, 'V'), + (0xFD40, 'X'), + (0xFD50, 'M', u'تجم'), + (0xFD51, 'M', u'تحج'), + (0xFD53, 'M', u'تحم'), + (0xFD54, 'M', u'تخم'), + (0xFD55, 'M', u'تمج'), + (0xFD56, 'M', u'تمح'), + (0xFD57, 'M', u'تمخ'), + (0xFD58, 'M', u'جمح'), + (0xFD5A, 'M', u'حمي'), + (0xFD5B, 'M', u'حمى'), + (0xFD5C, 'M', u'سحج'), + (0xFD5D, 'M', u'سجح'), + (0xFD5E, 'M', u'سجى'), + (0xFD5F, 'M', u'سمح'), + (0xFD61, 'M', u'سمج'), + (0xFD62, 'M', u'سمم'), + (0xFD64, 'M', u'صحح'), + (0xFD66, 'M', u'صمم'), + (0xFD67, 'M', u'شحم'), + (0xFD69, 'M', u'شجي'), + ] + +def _seg_48(): + return [ + (0xFD6A, 'M', u'شمخ'), + (0xFD6C, 'M', u'شمم'), + (0xFD6E, 'M', u'ضحى'), + (0xFD6F, 'M', u'ضخم'), + (0xFD71, 'M', u'طمح'), + (0xFD73, 'M', u'طمم'), + (0xFD74, 'M', u'طمي'), + (0xFD75, 'M', u'عجم'), + (0xFD76, 'M', u'عمم'), + (0xFD78, 'M', u'عمى'), + (0xFD79, 'M', u'غمم'), + (0xFD7A, 'M', u'غمي'), + (0xFD7B, 'M', u'غمى'), + (0xFD7C, 'M', u'فخم'), + (0xFD7E, 'M', u'قمح'), + (0xFD7F, 'M', u'قمم'), + (0xFD80, 'M', u'لحم'), + (0xFD81, 'M', u'لحي'), + (0xFD82, 'M', u'لحى'), + (0xFD83, 'M', u'لجج'), + (0xFD85, 'M', u'لخم'), + (0xFD87, 'M', u'لمح'), + (0xFD89, 'M', u'محج'), + (0xFD8A, 'M', u'محم'), + (0xFD8B, 'M', u'محي'), + (0xFD8C, 'M', u'مجح'), + (0xFD8D, 'M', u'مجم'), + (0xFD8E, 'M', u'مخج'), + (0xFD8F, 'M', u'مخم'), + (0xFD90, 'X'), + (0xFD92, 'M', u'مجخ'), + (0xFD93, 'M', u'همج'), + (0xFD94, 'M', u'همم'), + (0xFD95, 'M', u'نحم'), + (0xFD96, 'M', u'نحى'), + (0xFD97, 'M', u'نجم'), + (0xFD99, 'M', u'نجى'), + (0xFD9A, 'M', u'نمي'), + (0xFD9B, 'M', u'نمى'), + (0xFD9C, 'M', u'يمم'), + (0xFD9E, 'M', u'بخي'), + (0xFD9F, 'M', u'تجي'), + (0xFDA0, 'M', u'تجى'), + (0xFDA1, 'M', u'تخي'), + (0xFDA2, 'M', u'تخى'), + (0xFDA3, 'M', u'تمي'), + (0xFDA4, 'M', u'تمى'), + (0xFDA5, 'M', u'جمي'), + (0xFDA6, 'M', u'جحى'), + (0xFDA7, 'M', u'جمى'), + (0xFDA8, 'M', u'سخى'), + (0xFDA9, 'M', u'صحي'), + (0xFDAA, 'M', u'شحي'), + (0xFDAB, 'M', u'ضحي'), + (0xFDAC, 'M', u'لجي'), + (0xFDAD, 'M', u'لمي'), + (0xFDAE, 'M', u'يحي'), + (0xFDAF, 'M', u'يجي'), + (0xFDB0, 'M', u'يمي'), + (0xFDB1, 'M', u'ممي'), + (0xFDB2, 'M', u'قمي'), + (0xFDB3, 'M', u'نحي'), + (0xFDB4, 'M', u'قمح'), + (0xFDB5, 'M', u'لحم'), + (0xFDB6, 'M', u'عمي'), + (0xFDB7, 'M', u'كمي'), + (0xFDB8, 'M', u'نجح'), + (0xFDB9, 'M', u'مخي'), + (0xFDBA, 'M', u'لجم'), + (0xFDBB, 'M', u'كمم'), + (0xFDBC, 'M', u'لجم'), + (0xFDBD, 'M', u'نجح'), + (0xFDBE, 'M', u'جحي'), + (0xFDBF, 'M', u'حجي'), + (0xFDC0, 'M', u'مجي'), + (0xFDC1, 'M', u'فمي'), + (0xFDC2, 'M', u'بحي'), + (0xFDC3, 'M', u'كمم'), + (0xFDC4, 'M', u'عجم'), + (0xFDC5, 'M', u'صمم'), + (0xFDC6, 'M', u'سخي'), + (0xFDC7, 'M', u'نجي'), + (0xFDC8, 'X'), + (0xFDF0, 'M', u'صلے'), + (0xFDF1, 'M', u'قلے'), + (0xFDF2, 'M', u'الله'), + (0xFDF3, 'M', u'اكبر'), + (0xFDF4, 'M', u'محمد'), + (0xFDF5, 'M', u'صلعم'), + (0xFDF6, 'M', u'رسول'), + (0xFDF7, 'M', u'عليه'), + (0xFDF8, 'M', u'وسلم'), + (0xFDF9, 'M', u'صلى'), + (0xFDFA, '3', u'صلى الله عليه وسلم'), + (0xFDFB, '3', u'جل جلاله'), + (0xFDFC, 'M', u'ریال'), + (0xFDFD, 'V'), + (0xFDFE, 'X'), + (0xFE00, 'I'), + (0xFE10, '3', u','), + ] + +def _seg_49(): + return [ + (0xFE11, 'M', u'、'), + (0xFE12, 'X'), + (0xFE13, '3', u':'), + (0xFE14, '3', u';'), + (0xFE15, '3', u'!'), + (0xFE16, '3', u'?'), + (0xFE17, 'M', u'〖'), + (0xFE18, 'M', u'〗'), + (0xFE19, 'X'), + (0xFE20, 'V'), + (0xFE30, 'X'), + (0xFE31, 'M', u'—'), + (0xFE32, 'M', u'–'), + (0xFE33, '3', u'_'), + (0xFE35, '3', u'('), + (0xFE36, '3', u')'), + (0xFE37, '3', u'{'), + (0xFE38, '3', u'}'), + (0xFE39, 'M', u'〔'), + (0xFE3A, 'M', u'〕'), + (0xFE3B, 'M', u'【'), + (0xFE3C, 'M', u'】'), + (0xFE3D, 'M', u'《'), + (0xFE3E, 'M', u'》'), + (0xFE3F, 'M', u'〈'), + (0xFE40, 'M', u'〉'), + (0xFE41, 'M', u'「'), + (0xFE42, 'M', u'」'), + (0xFE43, 'M', u'『'), + (0xFE44, 'M', u'』'), + (0xFE45, 'V'), + (0xFE47, '3', u'['), + (0xFE48, '3', u']'), + (0xFE49, '3', u' ̅'), + (0xFE4D, '3', u'_'), + (0xFE50, '3', u','), + (0xFE51, 'M', u'、'), + (0xFE52, 'X'), + (0xFE54, '3', u';'), + (0xFE55, '3', u':'), + (0xFE56, '3', u'?'), + (0xFE57, '3', u'!'), + (0xFE58, 'M', u'—'), + (0xFE59, '3', u'('), + (0xFE5A, '3', u')'), + (0xFE5B, '3', u'{'), + (0xFE5C, '3', u'}'), + (0xFE5D, 'M', u'〔'), + (0xFE5E, 'M', u'〕'), + (0xFE5F, '3', u'#'), + (0xFE60, '3', u'&'), + (0xFE61, '3', u'*'), + (0xFE62, '3', u'+'), + (0xFE63, 'M', u'-'), + (0xFE64, '3', u'<'), + (0xFE65, '3', u'>'), + (0xFE66, '3', u'='), + (0xFE67, 'X'), + (0xFE68, '3', u'\\'), + (0xFE69, '3', u'$'), + (0xFE6A, '3', u'%'), + (0xFE6B, '3', u'@'), + (0xFE6C, 'X'), + (0xFE70, '3', u' ً'), + (0xFE71, 'M', u'ـً'), + (0xFE72, '3', u' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', u' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', u' َ'), + (0xFE77, 'M', u'ـَ'), + (0xFE78, '3', u' ُ'), + (0xFE79, 'M', u'ـُ'), + (0xFE7A, '3', u' ِ'), + (0xFE7B, 'M', u'ـِ'), + (0xFE7C, '3', u' ّ'), + (0xFE7D, 'M', u'ـّ'), + (0xFE7E, '3', u' ْ'), + (0xFE7F, 'M', u'ـْ'), + (0xFE80, 'M', u'ء'), + (0xFE81, 'M', u'آ'), + (0xFE83, 'M', u'أ'), + (0xFE85, 'M', u'ؤ'), + (0xFE87, 'M', u'إ'), + (0xFE89, 'M', u'ئ'), + (0xFE8D, 'M', u'ا'), + (0xFE8F, 'M', u'ب'), + (0xFE93, 'M', u'ة'), + (0xFE95, 'M', u'ت'), + (0xFE99, 'M', u'ث'), + (0xFE9D, 'M', u'ج'), + (0xFEA1, 'M', u'ح'), + (0xFEA5, 'M', u'خ'), + (0xFEA9, 'M', u'د'), + (0xFEAB, 'M', u'ذ'), + (0xFEAD, 'M', u'ر'), + (0xFEAF, 'M', u'ز'), + (0xFEB1, 'M', u'س'), + (0xFEB5, 'M', u'ش'), + (0xFEB9, 'M', u'ص'), + ] + +def _seg_50(): + return [ + (0xFEBD, 'M', u'ض'), + (0xFEC1, 'M', u'ط'), + (0xFEC5, 'M', u'ظ'), + (0xFEC9, 'M', u'ع'), + (0xFECD, 'M', u'غ'), + (0xFED1, 'M', u'ف'), + (0xFED5, 'M', u'ق'), + (0xFED9, 'M', u'ك'), + (0xFEDD, 'M', u'ل'), + (0xFEE1, 'M', u'م'), + (0xFEE5, 'M', u'ن'), + (0xFEE9, 'M', u'ه'), + (0xFEED, 'M', u'و'), + (0xFEEF, 'M', u'ى'), + (0xFEF1, 'M', u'ي'), + (0xFEF5, 'M', u'لآ'), + (0xFEF7, 'M', u'لأ'), + (0xFEF9, 'M', u'لإ'), + (0xFEFB, 'M', u'لا'), + (0xFEFD, 'X'), + (0xFEFF, 'I'), + (0xFF00, 'X'), + (0xFF01, '3', u'!'), + (0xFF02, '3', u'"'), + (0xFF03, '3', u'#'), + (0xFF04, '3', u'$'), + (0xFF05, '3', u'%'), + (0xFF06, '3', u'&'), + (0xFF07, '3', u'\''), + (0xFF08, '3', u'('), + (0xFF09, '3', u')'), + (0xFF0A, '3', u'*'), + (0xFF0B, '3', u'+'), + (0xFF0C, '3', u','), + (0xFF0D, 'M', u'-'), + (0xFF0E, 'M', u'.'), + (0xFF0F, '3', u'/'), + (0xFF10, 'M', u'0'), + (0xFF11, 'M', u'1'), + (0xFF12, 'M', u'2'), + (0xFF13, 'M', u'3'), + (0xFF14, 'M', u'4'), + (0xFF15, 'M', u'5'), + (0xFF16, 'M', u'6'), + (0xFF17, 'M', u'7'), + (0xFF18, 'M', u'8'), + (0xFF19, 'M', u'9'), + (0xFF1A, '3', u':'), + (0xFF1B, '3', u';'), + (0xFF1C, '3', u'<'), + (0xFF1D, '3', u'='), + (0xFF1E, '3', u'>'), + (0xFF1F, '3', u'?'), + (0xFF20, '3', u'@'), + (0xFF21, 'M', u'a'), + (0xFF22, 'M', u'b'), + (0xFF23, 'M', u'c'), + (0xFF24, 'M', u'd'), + (0xFF25, 'M', u'e'), + (0xFF26, 'M', u'f'), + (0xFF27, 'M', u'g'), + (0xFF28, 'M', u'h'), + (0xFF29, 'M', u'i'), + (0xFF2A, 'M', u'j'), + (0xFF2B, 'M', u'k'), + (0xFF2C, 'M', u'l'), + (0xFF2D, 'M', u'm'), + (0xFF2E, 'M', u'n'), + (0xFF2F, 'M', u'o'), + (0xFF30, 'M', u'p'), + (0xFF31, 'M', u'q'), + (0xFF32, 'M', u'r'), + (0xFF33, 'M', u's'), + (0xFF34, 'M', u't'), + (0xFF35, 'M', u'u'), + (0xFF36, 'M', u'v'), + (0xFF37, 'M', u'w'), + (0xFF38, 'M', u'x'), + (0xFF39, 'M', u'y'), + (0xFF3A, 'M', u'z'), + (0xFF3B, '3', u'['), + (0xFF3C, '3', u'\\'), + (0xFF3D, '3', u']'), + (0xFF3E, '3', u'^'), + (0xFF3F, '3', u'_'), + (0xFF40, '3', u'`'), + (0xFF41, 'M', u'a'), + (0xFF42, 'M', u'b'), + (0xFF43, 'M', u'c'), + (0xFF44, 'M', u'd'), + (0xFF45, 'M', u'e'), + (0xFF46, 'M', u'f'), + (0xFF47, 'M', u'g'), + (0xFF48, 'M', u'h'), + (0xFF49, 'M', u'i'), + (0xFF4A, 'M', u'j'), + (0xFF4B, 'M', u'k'), + (0xFF4C, 'M', u'l'), + (0xFF4D, 'M', u'm'), + (0xFF4E, 'M', u'n'), + ] + +def _seg_51(): + return [ + (0xFF4F, 'M', u'o'), + (0xFF50, 'M', u'p'), + (0xFF51, 'M', u'q'), + (0xFF52, 'M', u'r'), + (0xFF53, 'M', u's'), + (0xFF54, 'M', u't'), + (0xFF55, 'M', u'u'), + (0xFF56, 'M', u'v'), + (0xFF57, 'M', u'w'), + (0xFF58, 'M', u'x'), + (0xFF59, 'M', u'y'), + (0xFF5A, 'M', u'z'), + (0xFF5B, '3', u'{'), + (0xFF5C, '3', u'|'), + (0xFF5D, '3', u'}'), + (0xFF5E, '3', u'~'), + (0xFF5F, 'M', u'⦅'), + (0xFF60, 'M', u'⦆'), + (0xFF61, 'M', u'.'), + (0xFF62, 'M', u'「'), + (0xFF63, 'M', u'」'), + (0xFF64, 'M', u'、'), + (0xFF65, 'M', u'・'), + (0xFF66, 'M', u'ヲ'), + (0xFF67, 'M', u'ァ'), + (0xFF68, 'M', u'ィ'), + (0xFF69, 'M', u'ゥ'), + (0xFF6A, 'M', u'ェ'), + (0xFF6B, 'M', u'ォ'), + (0xFF6C, 'M', u'ャ'), + (0xFF6D, 'M', u'ュ'), + (0xFF6E, 'M', u'ョ'), + (0xFF6F, 'M', u'ッ'), + (0xFF70, 'M', u'ー'), + (0xFF71, 'M', u'ア'), + (0xFF72, 'M', u'イ'), + (0xFF73, 'M', u'ウ'), + (0xFF74, 'M', u'エ'), + (0xFF75, 'M', u'オ'), + (0xFF76, 'M', u'カ'), + (0xFF77, 'M', u'キ'), + (0xFF78, 'M', u'ク'), + (0xFF79, 'M', u'ケ'), + (0xFF7A, 'M', u'コ'), + (0xFF7B, 'M', u'サ'), + (0xFF7C, 'M', u'シ'), + (0xFF7D, 'M', u'ス'), + (0xFF7E, 'M', u'セ'), + (0xFF7F, 'M', u'ソ'), + (0xFF80, 'M', u'タ'), + (0xFF81, 'M', u'チ'), + (0xFF82, 'M', u'ツ'), + (0xFF83, 'M', u'テ'), + (0xFF84, 'M', u'ト'), + (0xFF85, 'M', u'ナ'), + (0xFF86, 'M', u'ニ'), + (0xFF87, 'M', u'ヌ'), + (0xFF88, 'M', u'ネ'), + (0xFF89, 'M', u'ノ'), + (0xFF8A, 'M', u'ハ'), + (0xFF8B, 'M', u'ヒ'), + (0xFF8C, 'M', u'フ'), + (0xFF8D, 'M', u'ヘ'), + (0xFF8E, 'M', u'ホ'), + (0xFF8F, 'M', u'マ'), + (0xFF90, 'M', u'ミ'), + (0xFF91, 'M', u'ム'), + (0xFF92, 'M', u'メ'), + (0xFF93, 'M', u'モ'), + (0xFF94, 'M', u'ヤ'), + (0xFF95, 'M', u'ユ'), + (0xFF96, 'M', u'ヨ'), + (0xFF97, 'M', u'ラ'), + (0xFF98, 'M', u'リ'), + (0xFF99, 'M', u'ル'), + (0xFF9A, 'M', u'レ'), + (0xFF9B, 'M', u'ロ'), + (0xFF9C, 'M', u'ワ'), + (0xFF9D, 'M', u'ン'), + (0xFF9E, 'M', u'゙'), + (0xFF9F, 'M', u'゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', u'ᄀ'), + (0xFFA2, 'M', u'ᄁ'), + (0xFFA3, 'M', u'ᆪ'), + (0xFFA4, 'M', u'ᄂ'), + (0xFFA5, 'M', u'ᆬ'), + (0xFFA6, 'M', u'ᆭ'), + (0xFFA7, 'M', u'ᄃ'), + (0xFFA8, 'M', u'ᄄ'), + (0xFFA9, 'M', u'ᄅ'), + (0xFFAA, 'M', u'ᆰ'), + (0xFFAB, 'M', u'ᆱ'), + (0xFFAC, 'M', u'ᆲ'), + (0xFFAD, 'M', u'ᆳ'), + (0xFFAE, 'M', u'ᆴ'), + (0xFFAF, 'M', u'ᆵ'), + (0xFFB0, 'M', u'ᄚ'), + (0xFFB1, 'M', u'ᄆ'), + (0xFFB2, 'M', u'ᄇ'), + ] + +def _seg_52(): + return [ + (0xFFB3, 'M', u'ᄈ'), + (0xFFB4, 'M', u'ᄡ'), + (0xFFB5, 'M', u'ᄉ'), + (0xFFB6, 'M', u'ᄊ'), + (0xFFB7, 'M', u'ᄋ'), + (0xFFB8, 'M', u'ᄌ'), + (0xFFB9, 'M', u'ᄍ'), + (0xFFBA, 'M', u'ᄎ'), + (0xFFBB, 'M', u'ᄏ'), + (0xFFBC, 'M', u'ᄐ'), + (0xFFBD, 'M', u'ᄑ'), + (0xFFBE, 'M', u'ᄒ'), + (0xFFBF, 'X'), + (0xFFC2, 'M', u'ᅡ'), + (0xFFC3, 'M', u'ᅢ'), + (0xFFC4, 'M', u'ᅣ'), + (0xFFC5, 'M', u'ᅤ'), + (0xFFC6, 'M', u'ᅥ'), + (0xFFC7, 'M', u'ᅦ'), + (0xFFC8, 'X'), + (0xFFCA, 'M', u'ᅧ'), + (0xFFCB, 'M', u'ᅨ'), + (0xFFCC, 'M', u'ᅩ'), + (0xFFCD, 'M', u'ᅪ'), + (0xFFCE, 'M', u'ᅫ'), + (0xFFCF, 'M', u'ᅬ'), + (0xFFD0, 'X'), + (0xFFD2, 'M', u'ᅭ'), + (0xFFD3, 'M', u'ᅮ'), + (0xFFD4, 'M', u'ᅯ'), + (0xFFD5, 'M', u'ᅰ'), + (0xFFD6, 'M', u'ᅱ'), + (0xFFD7, 'M', u'ᅲ'), + (0xFFD8, 'X'), + (0xFFDA, 'M', u'ᅳ'), + (0xFFDB, 'M', u'ᅴ'), + (0xFFDC, 'M', u'ᅵ'), + (0xFFDD, 'X'), + (0xFFE0, 'M', u'¢'), + (0xFFE1, 'M', u'£'), + (0xFFE2, 'M', u'¬'), + (0xFFE3, '3', u' ̄'), + (0xFFE4, 'M', u'¦'), + (0xFFE5, 'M', u'¥'), + (0xFFE6, 'M', u'₩'), + (0xFFE7, 'X'), + (0xFFE8, 'M', u'│'), + (0xFFE9, 'M', u'←'), + (0xFFEA, 'M', u'↑'), + (0xFFEB, 'M', u'→'), + (0xFFEC, 'M', u'↓'), + (0xFFED, 'M', u'■'), + (0xFFEE, 'M', u'○'), + (0xFFEF, 'X'), + (0x10000, 'V'), + (0x1000C, 'X'), + (0x1000D, 'V'), + (0x10027, 'X'), + (0x10028, 'V'), + (0x1003B, 'X'), + (0x1003C, 'V'), + (0x1003E, 'X'), + (0x1003F, 'V'), + (0x1004E, 'X'), + (0x10050, 'V'), + (0x1005E, 'X'), + (0x10080, 'V'), + (0x100FB, 'X'), + (0x10100, 'V'), + (0x10103, 'X'), + (0x10107, 'V'), + (0x10134, 'X'), + (0x10137, 'V'), + (0x1018F, 'X'), + (0x10190, 'V'), + (0x1019C, 'X'), + (0x101A0, 'V'), + (0x101A1, 'X'), + (0x101D0, 'V'), + (0x101FE, 'X'), + (0x10280, 'V'), + (0x1029D, 'X'), + (0x102A0, 'V'), + (0x102D1, 'X'), + (0x102E0, 'V'), + (0x102FC, 'X'), + (0x10300, 'V'), + (0x10324, 'X'), + (0x1032D, 'V'), + (0x1034B, 'X'), + (0x10350, 'V'), + (0x1037B, 'X'), + (0x10380, 'V'), + (0x1039E, 'X'), + (0x1039F, 'V'), + (0x103C4, 'X'), + (0x103C8, 'V'), + (0x103D6, 'X'), + (0x10400, 'M', u'𐐨'), + (0x10401, 'M', u'𐐩'), + ] + +def _seg_53(): + return [ + (0x10402, 'M', u'𐐪'), + (0x10403, 'M', u'𐐫'), + (0x10404, 'M', u'𐐬'), + (0x10405, 'M', u'𐐭'), + (0x10406, 'M', u'𐐮'), + (0x10407, 'M', u'𐐯'), + (0x10408, 'M', u'𐐰'), + (0x10409, 'M', u'𐐱'), + (0x1040A, 'M', u'𐐲'), + (0x1040B, 'M', u'𐐳'), + (0x1040C, 'M', u'𐐴'), + (0x1040D, 'M', u'𐐵'), + (0x1040E, 'M', u'𐐶'), + (0x1040F, 'M', u'𐐷'), + (0x10410, 'M', u'𐐸'), + (0x10411, 'M', u'𐐹'), + (0x10412, 'M', u'𐐺'), + (0x10413, 'M', u'𐐻'), + (0x10414, 'M', u'𐐼'), + (0x10415, 'M', u'𐐽'), + (0x10416, 'M', u'𐐾'), + (0x10417, 'M', u'𐐿'), + (0x10418, 'M', u'𐑀'), + (0x10419, 'M', u'𐑁'), + (0x1041A, 'M', u'𐑂'), + (0x1041B, 'M', u'𐑃'), + (0x1041C, 'M', u'𐑄'), + (0x1041D, 'M', u'𐑅'), + (0x1041E, 'M', u'𐑆'), + (0x1041F, 'M', u'𐑇'), + (0x10420, 'M', u'𐑈'), + (0x10421, 'M', u'𐑉'), + (0x10422, 'M', u'𐑊'), + (0x10423, 'M', u'𐑋'), + (0x10424, 'M', u'𐑌'), + (0x10425, 'M', u'𐑍'), + (0x10426, 'M', u'𐑎'), + (0x10427, 'M', u'𐑏'), + (0x10428, 'V'), + (0x1049E, 'X'), + (0x104A0, 'V'), + (0x104AA, 'X'), + (0x104B0, 'M', u'𐓘'), + (0x104B1, 'M', u'𐓙'), + (0x104B2, 'M', u'𐓚'), + (0x104B3, 'M', u'𐓛'), + (0x104B4, 'M', u'𐓜'), + (0x104B5, 'M', u'𐓝'), + (0x104B6, 'M', u'𐓞'), + (0x104B7, 'M', u'𐓟'), + (0x104B8, 'M', u'𐓠'), + (0x104B9, 'M', u'𐓡'), + (0x104BA, 'M', u'𐓢'), + (0x104BB, 'M', u'𐓣'), + (0x104BC, 'M', u'𐓤'), + (0x104BD, 'M', u'𐓥'), + (0x104BE, 'M', u'𐓦'), + (0x104BF, 'M', u'𐓧'), + (0x104C0, 'M', u'𐓨'), + (0x104C1, 'M', u'𐓩'), + (0x104C2, 'M', u'𐓪'), + (0x104C3, 'M', u'𐓫'), + (0x104C4, 'M', u'𐓬'), + (0x104C5, 'M', u'𐓭'), + (0x104C6, 'M', u'𐓮'), + (0x104C7, 'M', u'𐓯'), + (0x104C8, 'M', u'𐓰'), + (0x104C9, 'M', u'𐓱'), + (0x104CA, 'M', u'𐓲'), + (0x104CB, 'M', u'𐓳'), + (0x104CC, 'M', u'𐓴'), + (0x104CD, 'M', u'𐓵'), + (0x104CE, 'M', u'𐓶'), + (0x104CF, 'M', u'𐓷'), + (0x104D0, 'M', u'𐓸'), + (0x104D1, 'M', u'𐓹'), + (0x104D2, 'M', u'𐓺'), + (0x104D3, 'M', u'𐓻'), + (0x104D4, 'X'), + (0x104D8, 'V'), + (0x104FC, 'X'), + (0x10500, 'V'), + (0x10528, 'X'), + (0x10530, 'V'), + (0x10564, 'X'), + (0x1056F, 'V'), + (0x10570, 'X'), + (0x10600, 'V'), + (0x10737, 'X'), + (0x10740, 'V'), + (0x10756, 'X'), + (0x10760, 'V'), + (0x10768, 'X'), + (0x10800, 'V'), + (0x10806, 'X'), + (0x10808, 'V'), + (0x10809, 'X'), + (0x1080A, 'V'), + (0x10836, 'X'), + (0x10837, 'V'), + ] + +def _seg_54(): + return [ + (0x10839, 'X'), + (0x1083C, 'V'), + (0x1083D, 'X'), + (0x1083F, 'V'), + (0x10856, 'X'), + (0x10857, 'V'), + (0x1089F, 'X'), + (0x108A7, 'V'), + (0x108B0, 'X'), + (0x108E0, 'V'), + (0x108F3, 'X'), + (0x108F4, 'V'), + (0x108F6, 'X'), + (0x108FB, 'V'), + (0x1091C, 'X'), + (0x1091F, 'V'), + (0x1093A, 'X'), + (0x1093F, 'V'), + (0x10940, 'X'), + (0x10980, 'V'), + (0x109B8, 'X'), + (0x109BC, 'V'), + (0x109D0, 'X'), + (0x109D2, 'V'), + (0x10A04, 'X'), + (0x10A05, 'V'), + (0x10A07, 'X'), + (0x10A0C, 'V'), + (0x10A14, 'X'), + (0x10A15, 'V'), + (0x10A18, 'X'), + (0x10A19, 'V'), + (0x10A36, 'X'), + (0x10A38, 'V'), + (0x10A3B, 'X'), + (0x10A3F, 'V'), + (0x10A49, 'X'), + (0x10A50, 'V'), + (0x10A59, 'X'), + (0x10A60, 'V'), + (0x10AA0, 'X'), + (0x10AC0, 'V'), + (0x10AE7, 'X'), + (0x10AEB, 'V'), + (0x10AF7, 'X'), + (0x10B00, 'V'), + (0x10B36, 'X'), + (0x10B39, 'V'), + (0x10B56, 'X'), + (0x10B58, 'V'), + (0x10B73, 'X'), + (0x10B78, 'V'), + (0x10B92, 'X'), + (0x10B99, 'V'), + (0x10B9D, 'X'), + (0x10BA9, 'V'), + (0x10BB0, 'X'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', u'𐳀'), + (0x10C81, 'M', u'𐳁'), + (0x10C82, 'M', u'𐳂'), + (0x10C83, 'M', u'𐳃'), + (0x10C84, 'M', u'𐳄'), + (0x10C85, 'M', u'𐳅'), + (0x10C86, 'M', u'𐳆'), + (0x10C87, 'M', u'𐳇'), + (0x10C88, 'M', u'𐳈'), + (0x10C89, 'M', u'𐳉'), + (0x10C8A, 'M', u'𐳊'), + (0x10C8B, 'M', u'𐳋'), + (0x10C8C, 'M', u'𐳌'), + (0x10C8D, 'M', u'𐳍'), + (0x10C8E, 'M', u'𐳎'), + (0x10C8F, 'M', u'𐳏'), + (0x10C90, 'M', u'𐳐'), + (0x10C91, 'M', u'𐳑'), + (0x10C92, 'M', u'𐳒'), + (0x10C93, 'M', u'𐳓'), + (0x10C94, 'M', u'𐳔'), + (0x10C95, 'M', u'𐳕'), + (0x10C96, 'M', u'𐳖'), + (0x10C97, 'M', u'𐳗'), + (0x10C98, 'M', u'𐳘'), + (0x10C99, 'M', u'𐳙'), + (0x10C9A, 'M', u'𐳚'), + (0x10C9B, 'M', u'𐳛'), + (0x10C9C, 'M', u'𐳜'), + (0x10C9D, 'M', u'𐳝'), + (0x10C9E, 'M', u'𐳞'), + (0x10C9F, 'M', u'𐳟'), + (0x10CA0, 'M', u'𐳠'), + (0x10CA1, 'M', u'𐳡'), + (0x10CA2, 'M', u'𐳢'), + (0x10CA3, 'M', u'𐳣'), + (0x10CA4, 'M', u'𐳤'), + (0x10CA5, 'M', u'𐳥'), + (0x10CA6, 'M', u'𐳦'), + (0x10CA7, 'M', u'𐳧'), + (0x10CA8, 'M', u'𐳨'), + ] + +def _seg_55(): + return [ + (0x10CA9, 'M', u'𐳩'), + (0x10CAA, 'M', u'𐳪'), + (0x10CAB, 'M', u'𐳫'), + (0x10CAC, 'M', u'𐳬'), + (0x10CAD, 'M', u'𐳭'), + (0x10CAE, 'M', u'𐳮'), + (0x10CAF, 'M', u'𐳯'), + (0x10CB0, 'M', u'𐳰'), + (0x10CB1, 'M', u'𐳱'), + (0x10CB2, 'M', u'𐳲'), + (0x10CB3, 'X'), + (0x10CC0, 'V'), + (0x10CF3, 'X'), + (0x10CFA, 'V'), + (0x10D28, 'X'), + (0x10D30, 'V'), + (0x10D3A, 'X'), + (0x10E60, 'V'), + (0x10E7F, 'X'), + (0x10F00, 'V'), + (0x10F28, 'X'), + (0x10F30, 'V'), + (0x10F5A, 'X'), + (0x11000, 'V'), + (0x1104E, 'X'), + (0x11052, 'V'), + (0x11070, 'X'), + (0x1107F, 'V'), + (0x110BD, 'X'), + (0x110BE, 'V'), + (0x110C2, 'X'), + (0x110D0, 'V'), + (0x110E9, 'X'), + (0x110F0, 'V'), + (0x110FA, 'X'), + (0x11100, 'V'), + (0x11135, 'X'), + (0x11136, 'V'), + (0x11147, 'X'), + (0x11150, 'V'), + (0x11177, 'X'), + (0x11180, 'V'), + (0x111CE, 'X'), + (0x111D0, 'V'), + (0x111E0, 'X'), + (0x111E1, 'V'), + (0x111F5, 'X'), + (0x11200, 'V'), + (0x11212, 'X'), + (0x11213, 'V'), + (0x1123F, 'X'), + (0x11280, 'V'), + (0x11287, 'X'), + (0x11288, 'V'), + (0x11289, 'X'), + (0x1128A, 'V'), + (0x1128E, 'X'), + (0x1128F, 'V'), + (0x1129E, 'X'), + (0x1129F, 'V'), + (0x112AA, 'X'), + (0x112B0, 'V'), + (0x112EB, 'X'), + (0x112F0, 'V'), + (0x112FA, 'X'), + (0x11300, 'V'), + (0x11304, 'X'), + (0x11305, 'V'), + (0x1130D, 'X'), + (0x1130F, 'V'), + (0x11311, 'X'), + (0x11313, 'V'), + (0x11329, 'X'), + (0x1132A, 'V'), + (0x11331, 'X'), + (0x11332, 'V'), + (0x11334, 'X'), + (0x11335, 'V'), + (0x1133A, 'X'), + (0x1133B, 'V'), + (0x11345, 'X'), + (0x11347, 'V'), + (0x11349, 'X'), + (0x1134B, 'V'), + (0x1134E, 'X'), + (0x11350, 'V'), + (0x11351, 'X'), + (0x11357, 'V'), + (0x11358, 'X'), + (0x1135D, 'V'), + (0x11364, 'X'), + (0x11366, 'V'), + (0x1136D, 'X'), + (0x11370, 'V'), + (0x11375, 'X'), + (0x11400, 'V'), + (0x1145A, 'X'), + (0x1145B, 'V'), + (0x1145C, 'X'), + (0x1145D, 'V'), + ] + +def _seg_56(): + return [ + (0x1145F, 'X'), + (0x11480, 'V'), + (0x114C8, 'X'), + (0x114D0, 'V'), + (0x114DA, 'X'), + (0x11580, 'V'), + (0x115B6, 'X'), + (0x115B8, 'V'), + (0x115DE, 'X'), + (0x11600, 'V'), + (0x11645, 'X'), + (0x11650, 'V'), + (0x1165A, 'X'), + (0x11660, 'V'), + (0x1166D, 'X'), + (0x11680, 'V'), + (0x116B8, 'X'), + (0x116C0, 'V'), + (0x116CA, 'X'), + (0x11700, 'V'), + (0x1171B, 'X'), + (0x1171D, 'V'), + (0x1172C, 'X'), + (0x11730, 'V'), + (0x11740, 'X'), + (0x11800, 'V'), + (0x1183C, 'X'), + (0x118A0, 'M', u'𑣀'), + (0x118A1, 'M', u'𑣁'), + (0x118A2, 'M', u'𑣂'), + (0x118A3, 'M', u'𑣃'), + (0x118A4, 'M', u'𑣄'), + (0x118A5, 'M', u'𑣅'), + (0x118A6, 'M', u'𑣆'), + (0x118A7, 'M', u'𑣇'), + (0x118A8, 'M', u'𑣈'), + (0x118A9, 'M', u'𑣉'), + (0x118AA, 'M', u'𑣊'), + (0x118AB, 'M', u'𑣋'), + (0x118AC, 'M', u'𑣌'), + (0x118AD, 'M', u'𑣍'), + (0x118AE, 'M', u'𑣎'), + (0x118AF, 'M', u'𑣏'), + (0x118B0, 'M', u'𑣐'), + (0x118B1, 'M', u'𑣑'), + (0x118B2, 'M', u'𑣒'), + (0x118B3, 'M', u'𑣓'), + (0x118B4, 'M', u'𑣔'), + (0x118B5, 'M', u'𑣕'), + (0x118B6, 'M', u'𑣖'), + (0x118B7, 'M', u'𑣗'), + (0x118B8, 'M', u'𑣘'), + (0x118B9, 'M', u'𑣙'), + (0x118BA, 'M', u'𑣚'), + (0x118BB, 'M', u'𑣛'), + (0x118BC, 'M', u'𑣜'), + (0x118BD, 'M', u'𑣝'), + (0x118BE, 'M', u'𑣞'), + (0x118BF, 'M', u'𑣟'), + (0x118C0, 'V'), + (0x118F3, 'X'), + (0x118FF, 'V'), + (0x11900, 'X'), + (0x11A00, 'V'), + (0x11A48, 'X'), + (0x11A50, 'V'), + (0x11A84, 'X'), + (0x11A86, 'V'), + (0x11AA3, 'X'), + (0x11AC0, 'V'), + (0x11AF9, 'X'), + (0x11C00, 'V'), + (0x11C09, 'X'), + (0x11C0A, 'V'), + (0x11C37, 'X'), + (0x11C38, 'V'), + (0x11C46, 'X'), + (0x11C50, 'V'), + (0x11C6D, 'X'), + (0x11C70, 'V'), + (0x11C90, 'X'), + (0x11C92, 'V'), + (0x11CA8, 'X'), + (0x11CA9, 'V'), + (0x11CB7, 'X'), + (0x11D00, 'V'), + (0x11D07, 'X'), + (0x11D08, 'V'), + (0x11D0A, 'X'), + (0x11D0B, 'V'), + (0x11D37, 'X'), + (0x11D3A, 'V'), + (0x11D3B, 'X'), + (0x11D3C, 'V'), + (0x11D3E, 'X'), + (0x11D3F, 'V'), + (0x11D48, 'X'), + (0x11D50, 'V'), + (0x11D5A, 'X'), + (0x11D60, 'V'), + ] + +def _seg_57(): + return [ + (0x11D66, 'X'), + (0x11D67, 'V'), + (0x11D69, 'X'), + (0x11D6A, 'V'), + (0x11D8F, 'X'), + (0x11D90, 'V'), + (0x11D92, 'X'), + (0x11D93, 'V'), + (0x11D99, 'X'), + (0x11DA0, 'V'), + (0x11DAA, 'X'), + (0x11EE0, 'V'), + (0x11EF9, 'X'), + (0x12000, 'V'), + (0x1239A, 'X'), + (0x12400, 'V'), + (0x1246F, 'X'), + (0x12470, 'V'), + (0x12475, 'X'), + (0x12480, 'V'), + (0x12544, 'X'), + (0x13000, 'V'), + (0x1342F, 'X'), + (0x14400, 'V'), + (0x14647, 'X'), + (0x16800, 'V'), + (0x16A39, 'X'), + (0x16A40, 'V'), + (0x16A5F, 'X'), + (0x16A60, 'V'), + (0x16A6A, 'X'), + (0x16A6E, 'V'), + (0x16A70, 'X'), + (0x16AD0, 'V'), + (0x16AEE, 'X'), + (0x16AF0, 'V'), + (0x16AF6, 'X'), + (0x16B00, 'V'), + (0x16B46, 'X'), + (0x16B50, 'V'), + (0x16B5A, 'X'), + (0x16B5B, 'V'), + (0x16B62, 'X'), + (0x16B63, 'V'), + (0x16B78, 'X'), + (0x16B7D, 'V'), + (0x16B90, 'X'), + (0x16E60, 'V'), + (0x16E9B, 'X'), + (0x16F00, 'V'), + (0x16F45, 'X'), + (0x16F50, 'V'), + (0x16F7F, 'X'), + (0x16F8F, 'V'), + (0x16FA0, 'X'), + (0x16FE0, 'V'), + (0x16FE2, 'X'), + (0x17000, 'V'), + (0x187F2, 'X'), + (0x18800, 'V'), + (0x18AF3, 'X'), + (0x1B000, 'V'), + (0x1B11F, 'X'), + (0x1B170, 'V'), + (0x1B2FC, 'X'), + (0x1BC00, 'V'), + (0x1BC6B, 'X'), + (0x1BC70, 'V'), + (0x1BC7D, 'X'), + (0x1BC80, 'V'), + (0x1BC89, 'X'), + (0x1BC90, 'V'), + (0x1BC9A, 'X'), + (0x1BC9C, 'V'), + (0x1BCA0, 'I'), + (0x1BCA4, 'X'), + (0x1D000, 'V'), + (0x1D0F6, 'X'), + (0x1D100, 'V'), + (0x1D127, 'X'), + (0x1D129, 'V'), + (0x1D15E, 'M', u'𝅗𝅥'), + (0x1D15F, 'M', u'𝅘𝅥'), + (0x1D160, 'M', u'𝅘𝅥𝅮'), + (0x1D161, 'M', u'𝅘𝅥𝅯'), + (0x1D162, 'M', u'𝅘𝅥𝅰'), + (0x1D163, 'M', u'𝅘𝅥𝅱'), + (0x1D164, 'M', u'𝅘𝅥𝅲'), + (0x1D165, 'V'), + (0x1D173, 'X'), + (0x1D17B, 'V'), + (0x1D1BB, 'M', u'𝆹𝅥'), + (0x1D1BC, 'M', u'𝆺𝅥'), + (0x1D1BD, 'M', u'𝆹𝅥𝅮'), + (0x1D1BE, 'M', u'𝆺𝅥𝅮'), + (0x1D1BF, 'M', u'𝆹𝅥𝅯'), + (0x1D1C0, 'M', u'𝆺𝅥𝅯'), + (0x1D1C1, 'V'), + (0x1D1E9, 'X'), + (0x1D200, 'V'), + ] + +def _seg_58(): + return [ + (0x1D246, 'X'), + (0x1D2E0, 'V'), + (0x1D2F4, 'X'), + (0x1D300, 'V'), + (0x1D357, 'X'), + (0x1D360, 'V'), + (0x1D379, 'X'), + (0x1D400, 'M', u'a'), + (0x1D401, 'M', u'b'), + (0x1D402, 'M', u'c'), + (0x1D403, 'M', u'd'), + (0x1D404, 'M', u'e'), + (0x1D405, 'M', u'f'), + (0x1D406, 'M', u'g'), + (0x1D407, 'M', u'h'), + (0x1D408, 'M', u'i'), + (0x1D409, 'M', u'j'), + (0x1D40A, 'M', u'k'), + (0x1D40B, 'M', u'l'), + (0x1D40C, 'M', u'm'), + (0x1D40D, 'M', u'n'), + (0x1D40E, 'M', u'o'), + (0x1D40F, 'M', u'p'), + (0x1D410, 'M', u'q'), + (0x1D411, 'M', u'r'), + (0x1D412, 'M', u's'), + (0x1D413, 'M', u't'), + (0x1D414, 'M', u'u'), + (0x1D415, 'M', u'v'), + (0x1D416, 'M', u'w'), + (0x1D417, 'M', u'x'), + (0x1D418, 'M', u'y'), + (0x1D419, 'M', u'z'), + (0x1D41A, 'M', u'a'), + (0x1D41B, 'M', u'b'), + (0x1D41C, 'M', u'c'), + (0x1D41D, 'M', u'd'), + (0x1D41E, 'M', u'e'), + (0x1D41F, 'M', u'f'), + (0x1D420, 'M', u'g'), + (0x1D421, 'M', u'h'), + (0x1D422, 'M', u'i'), + (0x1D423, 'M', u'j'), + (0x1D424, 'M', u'k'), + (0x1D425, 'M', u'l'), + (0x1D426, 'M', u'm'), + (0x1D427, 'M', u'n'), + (0x1D428, 'M', u'o'), + (0x1D429, 'M', u'p'), + (0x1D42A, 'M', u'q'), + (0x1D42B, 'M', u'r'), + (0x1D42C, 'M', u's'), + (0x1D42D, 'M', u't'), + (0x1D42E, 'M', u'u'), + (0x1D42F, 'M', u'v'), + (0x1D430, 'M', u'w'), + (0x1D431, 'M', u'x'), + (0x1D432, 'M', u'y'), + (0x1D433, 'M', u'z'), + (0x1D434, 'M', u'a'), + (0x1D435, 'M', u'b'), + (0x1D436, 'M', u'c'), + (0x1D437, 'M', u'd'), + (0x1D438, 'M', u'e'), + (0x1D439, 'M', u'f'), + (0x1D43A, 'M', u'g'), + (0x1D43B, 'M', u'h'), + (0x1D43C, 'M', u'i'), + (0x1D43D, 'M', u'j'), + (0x1D43E, 'M', u'k'), + (0x1D43F, 'M', u'l'), + (0x1D440, 'M', u'm'), + (0x1D441, 'M', u'n'), + (0x1D442, 'M', u'o'), + (0x1D443, 'M', u'p'), + (0x1D444, 'M', u'q'), + (0x1D445, 'M', u'r'), + (0x1D446, 'M', u's'), + (0x1D447, 'M', u't'), + (0x1D448, 'M', u'u'), + (0x1D449, 'M', u'v'), + (0x1D44A, 'M', u'w'), + (0x1D44B, 'M', u'x'), + (0x1D44C, 'M', u'y'), + (0x1D44D, 'M', u'z'), + (0x1D44E, 'M', u'a'), + (0x1D44F, 'M', u'b'), + (0x1D450, 'M', u'c'), + (0x1D451, 'M', u'd'), + (0x1D452, 'M', u'e'), + (0x1D453, 'M', u'f'), + (0x1D454, 'M', u'g'), + (0x1D455, 'X'), + (0x1D456, 'M', u'i'), + (0x1D457, 'M', u'j'), + (0x1D458, 'M', u'k'), + (0x1D459, 'M', u'l'), + (0x1D45A, 'M', u'm'), + (0x1D45B, 'M', u'n'), + (0x1D45C, 'M', u'o'), + ] + +def _seg_59(): + return [ + (0x1D45D, 'M', u'p'), + (0x1D45E, 'M', u'q'), + (0x1D45F, 'M', u'r'), + (0x1D460, 'M', u's'), + (0x1D461, 'M', u't'), + (0x1D462, 'M', u'u'), + (0x1D463, 'M', u'v'), + (0x1D464, 'M', u'w'), + (0x1D465, 'M', u'x'), + (0x1D466, 'M', u'y'), + (0x1D467, 'M', u'z'), + (0x1D468, 'M', u'a'), + (0x1D469, 'M', u'b'), + (0x1D46A, 'M', u'c'), + (0x1D46B, 'M', u'd'), + (0x1D46C, 'M', u'e'), + (0x1D46D, 'M', u'f'), + (0x1D46E, 'M', u'g'), + (0x1D46F, 'M', u'h'), + (0x1D470, 'M', u'i'), + (0x1D471, 'M', u'j'), + (0x1D472, 'M', u'k'), + (0x1D473, 'M', u'l'), + (0x1D474, 'M', u'm'), + (0x1D475, 'M', u'n'), + (0x1D476, 'M', u'o'), + (0x1D477, 'M', u'p'), + (0x1D478, 'M', u'q'), + (0x1D479, 'M', u'r'), + (0x1D47A, 'M', u's'), + (0x1D47B, 'M', u't'), + (0x1D47C, 'M', u'u'), + (0x1D47D, 'M', u'v'), + (0x1D47E, 'M', u'w'), + (0x1D47F, 'M', u'x'), + (0x1D480, 'M', u'y'), + (0x1D481, 'M', u'z'), + (0x1D482, 'M', u'a'), + (0x1D483, 'M', u'b'), + (0x1D484, 'M', u'c'), + (0x1D485, 'M', u'd'), + (0x1D486, 'M', u'e'), + (0x1D487, 'M', u'f'), + (0x1D488, 'M', u'g'), + (0x1D489, 'M', u'h'), + (0x1D48A, 'M', u'i'), + (0x1D48B, 'M', u'j'), + (0x1D48C, 'M', u'k'), + (0x1D48D, 'M', u'l'), + (0x1D48E, 'M', u'm'), + (0x1D48F, 'M', u'n'), + (0x1D490, 'M', u'o'), + (0x1D491, 'M', u'p'), + (0x1D492, 'M', u'q'), + (0x1D493, 'M', u'r'), + (0x1D494, 'M', u's'), + (0x1D495, 'M', u't'), + (0x1D496, 'M', u'u'), + (0x1D497, 'M', u'v'), + (0x1D498, 'M', u'w'), + (0x1D499, 'M', u'x'), + (0x1D49A, 'M', u'y'), + (0x1D49B, 'M', u'z'), + (0x1D49C, 'M', u'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', u'c'), + (0x1D49F, 'M', u'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', u'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', u'j'), + (0x1D4A6, 'M', u'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', u'n'), + (0x1D4AA, 'M', u'o'), + (0x1D4AB, 'M', u'p'), + (0x1D4AC, 'M', u'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', u's'), + (0x1D4AF, 'M', u't'), + (0x1D4B0, 'M', u'u'), + (0x1D4B1, 'M', u'v'), + (0x1D4B2, 'M', u'w'), + (0x1D4B3, 'M', u'x'), + (0x1D4B4, 'M', u'y'), + (0x1D4B5, 'M', u'z'), + (0x1D4B6, 'M', u'a'), + (0x1D4B7, 'M', u'b'), + (0x1D4B8, 'M', u'c'), + (0x1D4B9, 'M', u'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', u'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', u'h'), + (0x1D4BE, 'M', u'i'), + (0x1D4BF, 'M', u'j'), + (0x1D4C0, 'M', u'k'), + (0x1D4C1, 'M', u'l'), + (0x1D4C2, 'M', u'm'), + (0x1D4C3, 'M', u'n'), + ] + +def _seg_60(): + return [ + (0x1D4C4, 'X'), + (0x1D4C5, 'M', u'p'), + (0x1D4C6, 'M', u'q'), + (0x1D4C7, 'M', u'r'), + (0x1D4C8, 'M', u's'), + (0x1D4C9, 'M', u't'), + (0x1D4CA, 'M', u'u'), + (0x1D4CB, 'M', u'v'), + (0x1D4CC, 'M', u'w'), + (0x1D4CD, 'M', u'x'), + (0x1D4CE, 'M', u'y'), + (0x1D4CF, 'M', u'z'), + (0x1D4D0, 'M', u'a'), + (0x1D4D1, 'M', u'b'), + (0x1D4D2, 'M', u'c'), + (0x1D4D3, 'M', u'd'), + (0x1D4D4, 'M', u'e'), + (0x1D4D5, 'M', u'f'), + (0x1D4D6, 'M', u'g'), + (0x1D4D7, 'M', u'h'), + (0x1D4D8, 'M', u'i'), + (0x1D4D9, 'M', u'j'), + (0x1D4DA, 'M', u'k'), + (0x1D4DB, 'M', u'l'), + (0x1D4DC, 'M', u'm'), + (0x1D4DD, 'M', u'n'), + (0x1D4DE, 'M', u'o'), + (0x1D4DF, 'M', u'p'), + (0x1D4E0, 'M', u'q'), + (0x1D4E1, 'M', u'r'), + (0x1D4E2, 'M', u's'), + (0x1D4E3, 'M', u't'), + (0x1D4E4, 'M', u'u'), + (0x1D4E5, 'M', u'v'), + (0x1D4E6, 'M', u'w'), + (0x1D4E7, 'M', u'x'), + (0x1D4E8, 'M', u'y'), + (0x1D4E9, 'M', u'z'), + (0x1D4EA, 'M', u'a'), + (0x1D4EB, 'M', u'b'), + (0x1D4EC, 'M', u'c'), + (0x1D4ED, 'M', u'd'), + (0x1D4EE, 'M', u'e'), + (0x1D4EF, 'M', u'f'), + (0x1D4F0, 'M', u'g'), + (0x1D4F1, 'M', u'h'), + (0x1D4F2, 'M', u'i'), + (0x1D4F3, 'M', u'j'), + (0x1D4F4, 'M', u'k'), + (0x1D4F5, 'M', u'l'), + (0x1D4F6, 'M', u'm'), + (0x1D4F7, 'M', u'n'), + (0x1D4F8, 'M', u'o'), + (0x1D4F9, 'M', u'p'), + (0x1D4FA, 'M', u'q'), + (0x1D4FB, 'M', u'r'), + (0x1D4FC, 'M', u's'), + (0x1D4FD, 'M', u't'), + (0x1D4FE, 'M', u'u'), + (0x1D4FF, 'M', u'v'), + (0x1D500, 'M', u'w'), + (0x1D501, 'M', u'x'), + (0x1D502, 'M', u'y'), + (0x1D503, 'M', u'z'), + (0x1D504, 'M', u'a'), + (0x1D505, 'M', u'b'), + (0x1D506, 'X'), + (0x1D507, 'M', u'd'), + (0x1D508, 'M', u'e'), + (0x1D509, 'M', u'f'), + (0x1D50A, 'M', u'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', u'j'), + (0x1D50E, 'M', u'k'), + (0x1D50F, 'M', u'l'), + (0x1D510, 'M', u'm'), + (0x1D511, 'M', u'n'), + (0x1D512, 'M', u'o'), + (0x1D513, 'M', u'p'), + (0x1D514, 'M', u'q'), + (0x1D515, 'X'), + (0x1D516, 'M', u's'), + (0x1D517, 'M', u't'), + (0x1D518, 'M', u'u'), + (0x1D519, 'M', u'v'), + (0x1D51A, 'M', u'w'), + (0x1D51B, 'M', u'x'), + (0x1D51C, 'M', u'y'), + (0x1D51D, 'X'), + (0x1D51E, 'M', u'a'), + (0x1D51F, 'M', u'b'), + (0x1D520, 'M', u'c'), + (0x1D521, 'M', u'd'), + (0x1D522, 'M', u'e'), + (0x1D523, 'M', u'f'), + (0x1D524, 'M', u'g'), + (0x1D525, 'M', u'h'), + (0x1D526, 'M', u'i'), + (0x1D527, 'M', u'j'), + (0x1D528, 'M', u'k'), + ] + +def _seg_61(): + return [ + (0x1D529, 'M', u'l'), + (0x1D52A, 'M', u'm'), + (0x1D52B, 'M', u'n'), + (0x1D52C, 'M', u'o'), + (0x1D52D, 'M', u'p'), + (0x1D52E, 'M', u'q'), + (0x1D52F, 'M', u'r'), + (0x1D530, 'M', u's'), + (0x1D531, 'M', u't'), + (0x1D532, 'M', u'u'), + (0x1D533, 'M', u'v'), + (0x1D534, 'M', u'w'), + (0x1D535, 'M', u'x'), + (0x1D536, 'M', u'y'), + (0x1D537, 'M', u'z'), + (0x1D538, 'M', u'a'), + (0x1D539, 'M', u'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', u'd'), + (0x1D53C, 'M', u'e'), + (0x1D53D, 'M', u'f'), + (0x1D53E, 'M', u'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', u'i'), + (0x1D541, 'M', u'j'), + (0x1D542, 'M', u'k'), + (0x1D543, 'M', u'l'), + (0x1D544, 'M', u'm'), + (0x1D545, 'X'), + (0x1D546, 'M', u'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', u's'), + (0x1D54B, 'M', u't'), + (0x1D54C, 'M', u'u'), + (0x1D54D, 'M', u'v'), + (0x1D54E, 'M', u'w'), + (0x1D54F, 'M', u'x'), + (0x1D550, 'M', u'y'), + (0x1D551, 'X'), + (0x1D552, 'M', u'a'), + (0x1D553, 'M', u'b'), + (0x1D554, 'M', u'c'), + (0x1D555, 'M', u'd'), + (0x1D556, 'M', u'e'), + (0x1D557, 'M', u'f'), + (0x1D558, 'M', u'g'), + (0x1D559, 'M', u'h'), + (0x1D55A, 'M', u'i'), + (0x1D55B, 'M', u'j'), + (0x1D55C, 'M', u'k'), + (0x1D55D, 'M', u'l'), + (0x1D55E, 'M', u'm'), + (0x1D55F, 'M', u'n'), + (0x1D560, 'M', u'o'), + (0x1D561, 'M', u'p'), + (0x1D562, 'M', u'q'), + (0x1D563, 'M', u'r'), + (0x1D564, 'M', u's'), + (0x1D565, 'M', u't'), + (0x1D566, 'M', u'u'), + (0x1D567, 'M', u'v'), + (0x1D568, 'M', u'w'), + (0x1D569, 'M', u'x'), + (0x1D56A, 'M', u'y'), + (0x1D56B, 'M', u'z'), + (0x1D56C, 'M', u'a'), + (0x1D56D, 'M', u'b'), + (0x1D56E, 'M', u'c'), + (0x1D56F, 'M', u'd'), + (0x1D570, 'M', u'e'), + (0x1D571, 'M', u'f'), + (0x1D572, 'M', u'g'), + (0x1D573, 'M', u'h'), + (0x1D574, 'M', u'i'), + (0x1D575, 'M', u'j'), + (0x1D576, 'M', u'k'), + (0x1D577, 'M', u'l'), + (0x1D578, 'M', u'm'), + (0x1D579, 'M', u'n'), + (0x1D57A, 'M', u'o'), + (0x1D57B, 'M', u'p'), + (0x1D57C, 'M', u'q'), + (0x1D57D, 'M', u'r'), + (0x1D57E, 'M', u's'), + (0x1D57F, 'M', u't'), + (0x1D580, 'M', u'u'), + (0x1D581, 'M', u'v'), + (0x1D582, 'M', u'w'), + (0x1D583, 'M', u'x'), + (0x1D584, 'M', u'y'), + (0x1D585, 'M', u'z'), + (0x1D586, 'M', u'a'), + (0x1D587, 'M', u'b'), + (0x1D588, 'M', u'c'), + (0x1D589, 'M', u'd'), + (0x1D58A, 'M', u'e'), + (0x1D58B, 'M', u'f'), + (0x1D58C, 'M', u'g'), + (0x1D58D, 'M', u'h'), + (0x1D58E, 'M', u'i'), + ] + +def _seg_62(): + return [ + (0x1D58F, 'M', u'j'), + (0x1D590, 'M', u'k'), + (0x1D591, 'M', u'l'), + (0x1D592, 'M', u'm'), + (0x1D593, 'M', u'n'), + (0x1D594, 'M', u'o'), + (0x1D595, 'M', u'p'), + (0x1D596, 'M', u'q'), + (0x1D597, 'M', u'r'), + (0x1D598, 'M', u's'), + (0x1D599, 'M', u't'), + (0x1D59A, 'M', u'u'), + (0x1D59B, 'M', u'v'), + (0x1D59C, 'M', u'w'), + (0x1D59D, 'M', u'x'), + (0x1D59E, 'M', u'y'), + (0x1D59F, 'M', u'z'), + (0x1D5A0, 'M', u'a'), + (0x1D5A1, 'M', u'b'), + (0x1D5A2, 'M', u'c'), + (0x1D5A3, 'M', u'd'), + (0x1D5A4, 'M', u'e'), + (0x1D5A5, 'M', u'f'), + (0x1D5A6, 'M', u'g'), + (0x1D5A7, 'M', u'h'), + (0x1D5A8, 'M', u'i'), + (0x1D5A9, 'M', u'j'), + (0x1D5AA, 'M', u'k'), + (0x1D5AB, 'M', u'l'), + (0x1D5AC, 'M', u'm'), + (0x1D5AD, 'M', u'n'), + (0x1D5AE, 'M', u'o'), + (0x1D5AF, 'M', u'p'), + (0x1D5B0, 'M', u'q'), + (0x1D5B1, 'M', u'r'), + (0x1D5B2, 'M', u's'), + (0x1D5B3, 'M', u't'), + (0x1D5B4, 'M', u'u'), + (0x1D5B5, 'M', u'v'), + (0x1D5B6, 'M', u'w'), + (0x1D5B7, 'M', u'x'), + (0x1D5B8, 'M', u'y'), + (0x1D5B9, 'M', u'z'), + (0x1D5BA, 'M', u'a'), + (0x1D5BB, 'M', u'b'), + (0x1D5BC, 'M', u'c'), + (0x1D5BD, 'M', u'd'), + (0x1D5BE, 'M', u'e'), + (0x1D5BF, 'M', u'f'), + (0x1D5C0, 'M', u'g'), + (0x1D5C1, 'M', u'h'), + (0x1D5C2, 'M', u'i'), + (0x1D5C3, 'M', u'j'), + (0x1D5C4, 'M', u'k'), + (0x1D5C5, 'M', u'l'), + (0x1D5C6, 'M', u'm'), + (0x1D5C7, 'M', u'n'), + (0x1D5C8, 'M', u'o'), + (0x1D5C9, 'M', u'p'), + (0x1D5CA, 'M', u'q'), + (0x1D5CB, 'M', u'r'), + (0x1D5CC, 'M', u's'), + (0x1D5CD, 'M', u't'), + (0x1D5CE, 'M', u'u'), + (0x1D5CF, 'M', u'v'), + (0x1D5D0, 'M', u'w'), + (0x1D5D1, 'M', u'x'), + (0x1D5D2, 'M', u'y'), + (0x1D5D3, 'M', u'z'), + (0x1D5D4, 'M', u'a'), + (0x1D5D5, 'M', u'b'), + (0x1D5D6, 'M', u'c'), + (0x1D5D7, 'M', u'd'), + (0x1D5D8, 'M', u'e'), + (0x1D5D9, 'M', u'f'), + (0x1D5DA, 'M', u'g'), + (0x1D5DB, 'M', u'h'), + (0x1D5DC, 'M', u'i'), + (0x1D5DD, 'M', u'j'), + (0x1D5DE, 'M', u'k'), + (0x1D5DF, 'M', u'l'), + (0x1D5E0, 'M', u'm'), + (0x1D5E1, 'M', u'n'), + (0x1D5E2, 'M', u'o'), + (0x1D5E3, 'M', u'p'), + (0x1D5E4, 'M', u'q'), + (0x1D5E5, 'M', u'r'), + (0x1D5E6, 'M', u's'), + (0x1D5E7, 'M', u't'), + (0x1D5E8, 'M', u'u'), + (0x1D5E9, 'M', u'v'), + (0x1D5EA, 'M', u'w'), + (0x1D5EB, 'M', u'x'), + (0x1D5EC, 'M', u'y'), + (0x1D5ED, 'M', u'z'), + (0x1D5EE, 'M', u'a'), + (0x1D5EF, 'M', u'b'), + (0x1D5F0, 'M', u'c'), + (0x1D5F1, 'M', u'd'), + (0x1D5F2, 'M', u'e'), + ] + +def _seg_63(): + return [ + (0x1D5F3, 'M', u'f'), + (0x1D5F4, 'M', u'g'), + (0x1D5F5, 'M', u'h'), + (0x1D5F6, 'M', u'i'), + (0x1D5F7, 'M', u'j'), + (0x1D5F8, 'M', u'k'), + (0x1D5F9, 'M', u'l'), + (0x1D5FA, 'M', u'm'), + (0x1D5FB, 'M', u'n'), + (0x1D5FC, 'M', u'o'), + (0x1D5FD, 'M', u'p'), + (0x1D5FE, 'M', u'q'), + (0x1D5FF, 'M', u'r'), + (0x1D600, 'M', u's'), + (0x1D601, 'M', u't'), + (0x1D602, 'M', u'u'), + (0x1D603, 'M', u'v'), + (0x1D604, 'M', u'w'), + (0x1D605, 'M', u'x'), + (0x1D606, 'M', u'y'), + (0x1D607, 'M', u'z'), + (0x1D608, 'M', u'a'), + (0x1D609, 'M', u'b'), + (0x1D60A, 'M', u'c'), + (0x1D60B, 'M', u'd'), + (0x1D60C, 'M', u'e'), + (0x1D60D, 'M', u'f'), + (0x1D60E, 'M', u'g'), + (0x1D60F, 'M', u'h'), + (0x1D610, 'M', u'i'), + (0x1D611, 'M', u'j'), + (0x1D612, 'M', u'k'), + (0x1D613, 'M', u'l'), + (0x1D614, 'M', u'm'), + (0x1D615, 'M', u'n'), + (0x1D616, 'M', u'o'), + (0x1D617, 'M', u'p'), + (0x1D618, 'M', u'q'), + (0x1D619, 'M', u'r'), + (0x1D61A, 'M', u's'), + (0x1D61B, 'M', u't'), + (0x1D61C, 'M', u'u'), + (0x1D61D, 'M', u'v'), + (0x1D61E, 'M', u'w'), + (0x1D61F, 'M', u'x'), + (0x1D620, 'M', u'y'), + (0x1D621, 'M', u'z'), + (0x1D622, 'M', u'a'), + (0x1D623, 'M', u'b'), + (0x1D624, 'M', u'c'), + (0x1D625, 'M', u'd'), + (0x1D626, 'M', u'e'), + (0x1D627, 'M', u'f'), + (0x1D628, 'M', u'g'), + (0x1D629, 'M', u'h'), + (0x1D62A, 'M', u'i'), + (0x1D62B, 'M', u'j'), + (0x1D62C, 'M', u'k'), + (0x1D62D, 'M', u'l'), + (0x1D62E, 'M', u'm'), + (0x1D62F, 'M', u'n'), + (0x1D630, 'M', u'o'), + (0x1D631, 'M', u'p'), + (0x1D632, 'M', u'q'), + (0x1D633, 'M', u'r'), + (0x1D634, 'M', u's'), + (0x1D635, 'M', u't'), + (0x1D636, 'M', u'u'), + (0x1D637, 'M', u'v'), + (0x1D638, 'M', u'w'), + (0x1D639, 'M', u'x'), + (0x1D63A, 'M', u'y'), + (0x1D63B, 'M', u'z'), + (0x1D63C, 'M', u'a'), + (0x1D63D, 'M', u'b'), + (0x1D63E, 'M', u'c'), + (0x1D63F, 'M', u'd'), + (0x1D640, 'M', u'e'), + (0x1D641, 'M', u'f'), + (0x1D642, 'M', u'g'), + (0x1D643, 'M', u'h'), + (0x1D644, 'M', u'i'), + (0x1D645, 'M', u'j'), + (0x1D646, 'M', u'k'), + (0x1D647, 'M', u'l'), + (0x1D648, 'M', u'm'), + (0x1D649, 'M', u'n'), + (0x1D64A, 'M', u'o'), + (0x1D64B, 'M', u'p'), + (0x1D64C, 'M', u'q'), + (0x1D64D, 'M', u'r'), + (0x1D64E, 'M', u's'), + (0x1D64F, 'M', u't'), + (0x1D650, 'M', u'u'), + (0x1D651, 'M', u'v'), + (0x1D652, 'M', u'w'), + (0x1D653, 'M', u'x'), + (0x1D654, 'M', u'y'), + (0x1D655, 'M', u'z'), + (0x1D656, 'M', u'a'), + ] + +def _seg_64(): + return [ + (0x1D657, 'M', u'b'), + (0x1D658, 'M', u'c'), + (0x1D659, 'M', u'd'), + (0x1D65A, 'M', u'e'), + (0x1D65B, 'M', u'f'), + (0x1D65C, 'M', u'g'), + (0x1D65D, 'M', u'h'), + (0x1D65E, 'M', u'i'), + (0x1D65F, 'M', u'j'), + (0x1D660, 'M', u'k'), + (0x1D661, 'M', u'l'), + (0x1D662, 'M', u'm'), + (0x1D663, 'M', u'n'), + (0x1D664, 'M', u'o'), + (0x1D665, 'M', u'p'), + (0x1D666, 'M', u'q'), + (0x1D667, 'M', u'r'), + (0x1D668, 'M', u's'), + (0x1D669, 'M', u't'), + (0x1D66A, 'M', u'u'), + (0x1D66B, 'M', u'v'), + (0x1D66C, 'M', u'w'), + (0x1D66D, 'M', u'x'), + (0x1D66E, 'M', u'y'), + (0x1D66F, 'M', u'z'), + (0x1D670, 'M', u'a'), + (0x1D671, 'M', u'b'), + (0x1D672, 'M', u'c'), + (0x1D673, 'M', u'd'), + (0x1D674, 'M', u'e'), + (0x1D675, 'M', u'f'), + (0x1D676, 'M', u'g'), + (0x1D677, 'M', u'h'), + (0x1D678, 'M', u'i'), + (0x1D679, 'M', u'j'), + (0x1D67A, 'M', u'k'), + (0x1D67B, 'M', u'l'), + (0x1D67C, 'M', u'm'), + (0x1D67D, 'M', u'n'), + (0x1D67E, 'M', u'o'), + (0x1D67F, 'M', u'p'), + (0x1D680, 'M', u'q'), + (0x1D681, 'M', u'r'), + (0x1D682, 'M', u's'), + (0x1D683, 'M', u't'), + (0x1D684, 'M', u'u'), + (0x1D685, 'M', u'v'), + (0x1D686, 'M', u'w'), + (0x1D687, 'M', u'x'), + (0x1D688, 'M', u'y'), + (0x1D689, 'M', u'z'), + (0x1D68A, 'M', u'a'), + (0x1D68B, 'M', u'b'), + (0x1D68C, 'M', u'c'), + (0x1D68D, 'M', u'd'), + (0x1D68E, 'M', u'e'), + (0x1D68F, 'M', u'f'), + (0x1D690, 'M', u'g'), + (0x1D691, 'M', u'h'), + (0x1D692, 'M', u'i'), + (0x1D693, 'M', u'j'), + (0x1D694, 'M', u'k'), + (0x1D695, 'M', u'l'), + (0x1D696, 'M', u'm'), + (0x1D697, 'M', u'n'), + (0x1D698, 'M', u'o'), + (0x1D699, 'M', u'p'), + (0x1D69A, 'M', u'q'), + (0x1D69B, 'M', u'r'), + (0x1D69C, 'M', u's'), + (0x1D69D, 'M', u't'), + (0x1D69E, 'M', u'u'), + (0x1D69F, 'M', u'v'), + (0x1D6A0, 'M', u'w'), + (0x1D6A1, 'M', u'x'), + (0x1D6A2, 'M', u'y'), + (0x1D6A3, 'M', u'z'), + (0x1D6A4, 'M', u'ı'), + (0x1D6A5, 'M', u'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', u'α'), + (0x1D6A9, 'M', u'β'), + (0x1D6AA, 'M', u'γ'), + (0x1D6AB, 'M', u'δ'), + (0x1D6AC, 'M', u'ε'), + (0x1D6AD, 'M', u'ζ'), + (0x1D6AE, 'M', u'η'), + (0x1D6AF, 'M', u'θ'), + (0x1D6B0, 'M', u'ι'), + (0x1D6B1, 'M', u'κ'), + (0x1D6B2, 'M', u'λ'), + (0x1D6B3, 'M', u'μ'), + (0x1D6B4, 'M', u'ν'), + (0x1D6B5, 'M', u'ξ'), + (0x1D6B6, 'M', u'ο'), + (0x1D6B7, 'M', u'π'), + (0x1D6B8, 'M', u'ρ'), + (0x1D6B9, 'M', u'θ'), + (0x1D6BA, 'M', u'σ'), + (0x1D6BB, 'M', u'τ'), + ] + +def _seg_65(): + return [ + (0x1D6BC, 'M', u'υ'), + (0x1D6BD, 'M', u'φ'), + (0x1D6BE, 'M', u'χ'), + (0x1D6BF, 'M', u'ψ'), + (0x1D6C0, 'M', u'ω'), + (0x1D6C1, 'M', u'∇'), + (0x1D6C2, 'M', u'α'), + (0x1D6C3, 'M', u'β'), + (0x1D6C4, 'M', u'γ'), + (0x1D6C5, 'M', u'δ'), + (0x1D6C6, 'M', u'ε'), + (0x1D6C7, 'M', u'ζ'), + (0x1D6C8, 'M', u'η'), + (0x1D6C9, 'M', u'θ'), + (0x1D6CA, 'M', u'ι'), + (0x1D6CB, 'M', u'κ'), + (0x1D6CC, 'M', u'λ'), + (0x1D6CD, 'M', u'μ'), + (0x1D6CE, 'M', u'ν'), + (0x1D6CF, 'M', u'ξ'), + (0x1D6D0, 'M', u'ο'), + (0x1D6D1, 'M', u'π'), + (0x1D6D2, 'M', u'ρ'), + (0x1D6D3, 'M', u'σ'), + (0x1D6D5, 'M', u'τ'), + (0x1D6D6, 'M', u'υ'), + (0x1D6D7, 'M', u'φ'), + (0x1D6D8, 'M', u'χ'), + (0x1D6D9, 'M', u'ψ'), + (0x1D6DA, 'M', u'ω'), + (0x1D6DB, 'M', u'∂'), + (0x1D6DC, 'M', u'ε'), + (0x1D6DD, 'M', u'θ'), + (0x1D6DE, 'M', u'κ'), + (0x1D6DF, 'M', u'φ'), + (0x1D6E0, 'M', u'ρ'), + (0x1D6E1, 'M', u'π'), + (0x1D6E2, 'M', u'α'), + (0x1D6E3, 'M', u'β'), + (0x1D6E4, 'M', u'γ'), + (0x1D6E5, 'M', u'δ'), + (0x1D6E6, 'M', u'ε'), + (0x1D6E7, 'M', u'ζ'), + (0x1D6E8, 'M', u'η'), + (0x1D6E9, 'M', u'θ'), + (0x1D6EA, 'M', u'ι'), + (0x1D6EB, 'M', u'κ'), + (0x1D6EC, 'M', u'λ'), + (0x1D6ED, 'M', u'μ'), + (0x1D6EE, 'M', u'ν'), + (0x1D6EF, 'M', u'ξ'), + (0x1D6F0, 'M', u'ο'), + (0x1D6F1, 'M', u'π'), + (0x1D6F2, 'M', u'ρ'), + (0x1D6F3, 'M', u'θ'), + (0x1D6F4, 'M', u'σ'), + (0x1D6F5, 'M', u'τ'), + (0x1D6F6, 'M', u'υ'), + (0x1D6F7, 'M', u'φ'), + (0x1D6F8, 'M', u'χ'), + (0x1D6F9, 'M', u'ψ'), + (0x1D6FA, 'M', u'ω'), + (0x1D6FB, 'M', u'∇'), + (0x1D6FC, 'M', u'α'), + (0x1D6FD, 'M', u'β'), + (0x1D6FE, 'M', u'γ'), + (0x1D6FF, 'M', u'δ'), + (0x1D700, 'M', u'ε'), + (0x1D701, 'M', u'ζ'), + (0x1D702, 'M', u'η'), + (0x1D703, 'M', u'θ'), + (0x1D704, 'M', u'ι'), + (0x1D705, 'M', u'κ'), + (0x1D706, 'M', u'λ'), + (0x1D707, 'M', u'μ'), + (0x1D708, 'M', u'ν'), + (0x1D709, 'M', u'ξ'), + (0x1D70A, 'M', u'ο'), + (0x1D70B, 'M', u'π'), + (0x1D70C, 'M', u'ρ'), + (0x1D70D, 'M', u'σ'), + (0x1D70F, 'M', u'τ'), + (0x1D710, 'M', u'υ'), + (0x1D711, 'M', u'φ'), + (0x1D712, 'M', u'χ'), + (0x1D713, 'M', u'ψ'), + (0x1D714, 'M', u'ω'), + (0x1D715, 'M', u'∂'), + (0x1D716, 'M', u'ε'), + (0x1D717, 'M', u'θ'), + (0x1D718, 'M', u'κ'), + (0x1D719, 'M', u'φ'), + (0x1D71A, 'M', u'ρ'), + (0x1D71B, 'M', u'π'), + (0x1D71C, 'M', u'α'), + (0x1D71D, 'M', u'β'), + (0x1D71E, 'M', u'γ'), + (0x1D71F, 'M', u'δ'), + (0x1D720, 'M', u'ε'), + (0x1D721, 'M', u'ζ'), + ] + +def _seg_66(): + return [ + (0x1D722, 'M', u'η'), + (0x1D723, 'M', u'θ'), + (0x1D724, 'M', u'ι'), + (0x1D725, 'M', u'κ'), + (0x1D726, 'M', u'λ'), + (0x1D727, 'M', u'μ'), + (0x1D728, 'M', u'ν'), + (0x1D729, 'M', u'ξ'), + (0x1D72A, 'M', u'ο'), + (0x1D72B, 'M', u'π'), + (0x1D72C, 'M', u'ρ'), + (0x1D72D, 'M', u'θ'), + (0x1D72E, 'M', u'σ'), + (0x1D72F, 'M', u'τ'), + (0x1D730, 'M', u'υ'), + (0x1D731, 'M', u'φ'), + (0x1D732, 'M', u'χ'), + (0x1D733, 'M', u'ψ'), + (0x1D734, 'M', u'ω'), + (0x1D735, 'M', u'∇'), + (0x1D736, 'M', u'α'), + (0x1D737, 'M', u'β'), + (0x1D738, 'M', u'γ'), + (0x1D739, 'M', u'δ'), + (0x1D73A, 'M', u'ε'), + (0x1D73B, 'M', u'ζ'), + (0x1D73C, 'M', u'η'), + (0x1D73D, 'M', u'θ'), + (0x1D73E, 'M', u'ι'), + (0x1D73F, 'M', u'κ'), + (0x1D740, 'M', u'λ'), + (0x1D741, 'M', u'μ'), + (0x1D742, 'M', u'ν'), + (0x1D743, 'M', u'ξ'), + (0x1D744, 'M', u'ο'), + (0x1D745, 'M', u'π'), + (0x1D746, 'M', u'ρ'), + (0x1D747, 'M', u'σ'), + (0x1D749, 'M', u'τ'), + (0x1D74A, 'M', u'υ'), + (0x1D74B, 'M', u'φ'), + (0x1D74C, 'M', u'χ'), + (0x1D74D, 'M', u'ψ'), + (0x1D74E, 'M', u'ω'), + (0x1D74F, 'M', u'∂'), + (0x1D750, 'M', u'ε'), + (0x1D751, 'M', u'θ'), + (0x1D752, 'M', u'κ'), + (0x1D753, 'M', u'φ'), + (0x1D754, 'M', u'ρ'), + (0x1D755, 'M', u'π'), + (0x1D756, 'M', u'α'), + (0x1D757, 'M', u'β'), + (0x1D758, 'M', u'γ'), + (0x1D759, 'M', u'δ'), + (0x1D75A, 'M', u'ε'), + (0x1D75B, 'M', u'ζ'), + (0x1D75C, 'M', u'η'), + (0x1D75D, 'M', u'θ'), + (0x1D75E, 'M', u'ι'), + (0x1D75F, 'M', u'κ'), + (0x1D760, 'M', u'λ'), + (0x1D761, 'M', u'μ'), + (0x1D762, 'M', u'ν'), + (0x1D763, 'M', u'ξ'), + (0x1D764, 'M', u'ο'), + (0x1D765, 'M', u'π'), + (0x1D766, 'M', u'ρ'), + (0x1D767, 'M', u'θ'), + (0x1D768, 'M', u'σ'), + (0x1D769, 'M', u'τ'), + (0x1D76A, 'M', u'υ'), + (0x1D76B, 'M', u'φ'), + (0x1D76C, 'M', u'χ'), + (0x1D76D, 'M', u'ψ'), + (0x1D76E, 'M', u'ω'), + (0x1D76F, 'M', u'∇'), + (0x1D770, 'M', u'α'), + (0x1D771, 'M', u'β'), + (0x1D772, 'M', u'γ'), + (0x1D773, 'M', u'δ'), + (0x1D774, 'M', u'ε'), + (0x1D775, 'M', u'ζ'), + (0x1D776, 'M', u'η'), + (0x1D777, 'M', u'θ'), + (0x1D778, 'M', u'ι'), + (0x1D779, 'M', u'κ'), + (0x1D77A, 'M', u'λ'), + (0x1D77B, 'M', u'μ'), + (0x1D77C, 'M', u'ν'), + (0x1D77D, 'M', u'ξ'), + (0x1D77E, 'M', u'ο'), + (0x1D77F, 'M', u'π'), + (0x1D780, 'M', u'ρ'), + (0x1D781, 'M', u'σ'), + (0x1D783, 'M', u'τ'), + (0x1D784, 'M', u'υ'), + (0x1D785, 'M', u'φ'), + (0x1D786, 'M', u'χ'), + (0x1D787, 'M', u'ψ'), + ] + +def _seg_67(): + return [ + (0x1D788, 'M', u'ω'), + (0x1D789, 'M', u'∂'), + (0x1D78A, 'M', u'ε'), + (0x1D78B, 'M', u'θ'), + (0x1D78C, 'M', u'κ'), + (0x1D78D, 'M', u'φ'), + (0x1D78E, 'M', u'ρ'), + (0x1D78F, 'M', u'π'), + (0x1D790, 'M', u'α'), + (0x1D791, 'M', u'β'), + (0x1D792, 'M', u'γ'), + (0x1D793, 'M', u'δ'), + (0x1D794, 'M', u'ε'), + (0x1D795, 'M', u'ζ'), + (0x1D796, 'M', u'η'), + (0x1D797, 'M', u'θ'), + (0x1D798, 'M', u'ι'), + (0x1D799, 'M', u'κ'), + (0x1D79A, 'M', u'λ'), + (0x1D79B, 'M', u'μ'), + (0x1D79C, 'M', u'ν'), + (0x1D79D, 'M', u'ξ'), + (0x1D79E, 'M', u'ο'), + (0x1D79F, 'M', u'π'), + (0x1D7A0, 'M', u'ρ'), + (0x1D7A1, 'M', u'θ'), + (0x1D7A2, 'M', u'σ'), + (0x1D7A3, 'M', u'τ'), + (0x1D7A4, 'M', u'υ'), + (0x1D7A5, 'M', u'φ'), + (0x1D7A6, 'M', u'χ'), + (0x1D7A7, 'M', u'ψ'), + (0x1D7A8, 'M', u'ω'), + (0x1D7A9, 'M', u'∇'), + (0x1D7AA, 'M', u'α'), + (0x1D7AB, 'M', u'β'), + (0x1D7AC, 'M', u'γ'), + (0x1D7AD, 'M', u'δ'), + (0x1D7AE, 'M', u'ε'), + (0x1D7AF, 'M', u'ζ'), + (0x1D7B0, 'M', u'η'), + (0x1D7B1, 'M', u'θ'), + (0x1D7B2, 'M', u'ι'), + (0x1D7B3, 'M', u'κ'), + (0x1D7B4, 'M', u'λ'), + (0x1D7B5, 'M', u'μ'), + (0x1D7B6, 'M', u'ν'), + (0x1D7B7, 'M', u'ξ'), + (0x1D7B8, 'M', u'ο'), + (0x1D7B9, 'M', u'π'), + (0x1D7BA, 'M', u'ρ'), + (0x1D7BB, 'M', u'σ'), + (0x1D7BD, 'M', u'τ'), + (0x1D7BE, 'M', u'υ'), + (0x1D7BF, 'M', u'φ'), + (0x1D7C0, 'M', u'χ'), + (0x1D7C1, 'M', u'ψ'), + (0x1D7C2, 'M', u'ω'), + (0x1D7C3, 'M', u'∂'), + (0x1D7C4, 'M', u'ε'), + (0x1D7C5, 'M', u'θ'), + (0x1D7C6, 'M', u'κ'), + (0x1D7C7, 'M', u'φ'), + (0x1D7C8, 'M', u'ρ'), + (0x1D7C9, 'M', u'π'), + (0x1D7CA, 'M', u'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', u'0'), + (0x1D7CF, 'M', u'1'), + (0x1D7D0, 'M', u'2'), + (0x1D7D1, 'M', u'3'), + (0x1D7D2, 'M', u'4'), + (0x1D7D3, 'M', u'5'), + (0x1D7D4, 'M', u'6'), + (0x1D7D5, 'M', u'7'), + (0x1D7D6, 'M', u'8'), + (0x1D7D7, 'M', u'9'), + (0x1D7D8, 'M', u'0'), + (0x1D7D9, 'M', u'1'), + (0x1D7DA, 'M', u'2'), + (0x1D7DB, 'M', u'3'), + (0x1D7DC, 'M', u'4'), + (0x1D7DD, 'M', u'5'), + (0x1D7DE, 'M', u'6'), + (0x1D7DF, 'M', u'7'), + (0x1D7E0, 'M', u'8'), + (0x1D7E1, 'M', u'9'), + (0x1D7E2, 'M', u'0'), + (0x1D7E3, 'M', u'1'), + (0x1D7E4, 'M', u'2'), + (0x1D7E5, 'M', u'3'), + (0x1D7E6, 'M', u'4'), + (0x1D7E7, 'M', u'5'), + (0x1D7E8, 'M', u'6'), + (0x1D7E9, 'M', u'7'), + (0x1D7EA, 'M', u'8'), + (0x1D7EB, 'M', u'9'), + (0x1D7EC, 'M', u'0'), + (0x1D7ED, 'M', u'1'), + (0x1D7EE, 'M', u'2'), + ] + +def _seg_68(): + return [ + (0x1D7EF, 'M', u'3'), + (0x1D7F0, 'M', u'4'), + (0x1D7F1, 'M', u'5'), + (0x1D7F2, 'M', u'6'), + (0x1D7F3, 'M', u'7'), + (0x1D7F4, 'M', u'8'), + (0x1D7F5, 'M', u'9'), + (0x1D7F6, 'M', u'0'), + (0x1D7F7, 'M', u'1'), + (0x1D7F8, 'M', u'2'), + (0x1D7F9, 'M', u'3'), + (0x1D7FA, 'M', u'4'), + (0x1D7FB, 'M', u'5'), + (0x1D7FC, 'M', u'6'), + (0x1D7FD, 'M', u'7'), + (0x1D7FE, 'M', u'8'), + (0x1D7FF, 'M', u'9'), + (0x1D800, 'V'), + (0x1DA8C, 'X'), + (0x1DA9B, 'V'), + (0x1DAA0, 'X'), + (0x1DAA1, 'V'), + (0x1DAB0, 'X'), + (0x1E000, 'V'), + (0x1E007, 'X'), + (0x1E008, 'V'), + (0x1E019, 'X'), + (0x1E01B, 'V'), + (0x1E022, 'X'), + (0x1E023, 'V'), + (0x1E025, 'X'), + (0x1E026, 'V'), + (0x1E02B, 'X'), + (0x1E800, 'V'), + (0x1E8C5, 'X'), + (0x1E8C7, 'V'), + (0x1E8D7, 'X'), + (0x1E900, 'M', u'𞤢'), + (0x1E901, 'M', u'𞤣'), + (0x1E902, 'M', u'𞤤'), + (0x1E903, 'M', u'𞤥'), + (0x1E904, 'M', u'𞤦'), + (0x1E905, 'M', u'𞤧'), + (0x1E906, 'M', u'𞤨'), + (0x1E907, 'M', u'𞤩'), + (0x1E908, 'M', u'𞤪'), + (0x1E909, 'M', u'𞤫'), + (0x1E90A, 'M', u'𞤬'), + (0x1E90B, 'M', u'𞤭'), + (0x1E90C, 'M', u'𞤮'), + (0x1E90D, 'M', u'𞤯'), + (0x1E90E, 'M', u'𞤰'), + (0x1E90F, 'M', u'𞤱'), + (0x1E910, 'M', u'𞤲'), + (0x1E911, 'M', u'𞤳'), + (0x1E912, 'M', u'𞤴'), + (0x1E913, 'M', u'𞤵'), + (0x1E914, 'M', u'𞤶'), + (0x1E915, 'M', u'𞤷'), + (0x1E916, 'M', u'𞤸'), + (0x1E917, 'M', u'𞤹'), + (0x1E918, 'M', u'𞤺'), + (0x1E919, 'M', u'𞤻'), + (0x1E91A, 'M', u'𞤼'), + (0x1E91B, 'M', u'𞤽'), + (0x1E91C, 'M', u'𞤾'), + (0x1E91D, 'M', u'𞤿'), + (0x1E91E, 'M', u'𞥀'), + (0x1E91F, 'M', u'𞥁'), + (0x1E920, 'M', u'𞥂'), + (0x1E921, 'M', u'𞥃'), + (0x1E922, 'V'), + (0x1E94B, 'X'), + (0x1E950, 'V'), + (0x1E95A, 'X'), + (0x1E95E, 'V'), + (0x1E960, 'X'), + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1EE00, 'M', u'ا'), + (0x1EE01, 'M', u'ب'), + (0x1EE02, 'M', u'ج'), + (0x1EE03, 'M', u'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', u'و'), + (0x1EE06, 'M', u'ز'), + (0x1EE07, 'M', u'ح'), + (0x1EE08, 'M', u'ط'), + (0x1EE09, 'M', u'ي'), + (0x1EE0A, 'M', u'ك'), + (0x1EE0B, 'M', u'ل'), + (0x1EE0C, 'M', u'م'), + (0x1EE0D, 'M', u'ن'), + (0x1EE0E, 'M', u'س'), + (0x1EE0F, 'M', u'ع'), + (0x1EE10, 'M', u'ف'), + (0x1EE11, 'M', u'ص'), + (0x1EE12, 'M', u'ق'), + (0x1EE13, 'M', u'ر'), + (0x1EE14, 'M', u'ش'), + ] + +def _seg_69(): + return [ + (0x1EE15, 'M', u'ت'), + (0x1EE16, 'M', u'ث'), + (0x1EE17, 'M', u'خ'), + (0x1EE18, 'M', u'ذ'), + (0x1EE19, 'M', u'ض'), + (0x1EE1A, 'M', u'ظ'), + (0x1EE1B, 'M', u'غ'), + (0x1EE1C, 'M', u'ٮ'), + (0x1EE1D, 'M', u'ں'), + (0x1EE1E, 'M', u'ڡ'), + (0x1EE1F, 'M', u'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', u'ب'), + (0x1EE22, 'M', u'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', u'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', u'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', u'ي'), + (0x1EE2A, 'M', u'ك'), + (0x1EE2B, 'M', u'ل'), + (0x1EE2C, 'M', u'م'), + (0x1EE2D, 'M', u'ن'), + (0x1EE2E, 'M', u'س'), + (0x1EE2F, 'M', u'ع'), + (0x1EE30, 'M', u'ف'), + (0x1EE31, 'M', u'ص'), + (0x1EE32, 'M', u'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', u'ش'), + (0x1EE35, 'M', u'ت'), + (0x1EE36, 'M', u'ث'), + (0x1EE37, 'M', u'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', u'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', u'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', u'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', u'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', u'ي'), + (0x1EE4A, 'X'), + (0x1EE4B, 'M', u'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', u'ن'), + (0x1EE4E, 'M', u'س'), + (0x1EE4F, 'M', u'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', u'ص'), + (0x1EE52, 'M', u'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', u'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', u'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', u'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', u'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', u'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', u'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', u'ب'), + (0x1EE62, 'M', u'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', u'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', u'ح'), + (0x1EE68, 'M', u'ط'), + (0x1EE69, 'M', u'ي'), + (0x1EE6A, 'M', u'ك'), + (0x1EE6B, 'X'), + (0x1EE6C, 'M', u'م'), + (0x1EE6D, 'M', u'ن'), + (0x1EE6E, 'M', u'س'), + (0x1EE6F, 'M', u'ع'), + (0x1EE70, 'M', u'ف'), + (0x1EE71, 'M', u'ص'), + (0x1EE72, 'M', u'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', u'ش'), + (0x1EE75, 'M', u'ت'), + (0x1EE76, 'M', u'ث'), + (0x1EE77, 'M', u'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', u'ض'), + (0x1EE7A, 'M', u'ظ'), + (0x1EE7B, 'M', u'غ'), + (0x1EE7C, 'M', u'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', u'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', u'ا'), + (0x1EE81, 'M', u'ب'), + (0x1EE82, 'M', u'ج'), + (0x1EE83, 'M', u'د'), + ] + +def _seg_70(): + return [ + (0x1EE84, 'M', u'ه'), + (0x1EE85, 'M', u'و'), + (0x1EE86, 'M', u'ز'), + (0x1EE87, 'M', u'ح'), + (0x1EE88, 'M', u'ط'), + (0x1EE89, 'M', u'ي'), + (0x1EE8A, 'X'), + (0x1EE8B, 'M', u'ل'), + (0x1EE8C, 'M', u'م'), + (0x1EE8D, 'M', u'ن'), + (0x1EE8E, 'M', u'س'), + (0x1EE8F, 'M', u'ع'), + (0x1EE90, 'M', u'ف'), + (0x1EE91, 'M', u'ص'), + (0x1EE92, 'M', u'ق'), + (0x1EE93, 'M', u'ر'), + (0x1EE94, 'M', u'ش'), + (0x1EE95, 'M', u'ت'), + (0x1EE96, 'M', u'ث'), + (0x1EE97, 'M', u'خ'), + (0x1EE98, 'M', u'ذ'), + (0x1EE99, 'M', u'ض'), + (0x1EE9A, 'M', u'ظ'), + (0x1EE9B, 'M', u'غ'), + (0x1EE9C, 'X'), + (0x1EEA1, 'M', u'ب'), + (0x1EEA2, 'M', u'ج'), + (0x1EEA3, 'M', u'د'), + (0x1EEA4, 'X'), + (0x1EEA5, 'M', u'و'), + (0x1EEA6, 'M', u'ز'), + (0x1EEA7, 'M', u'ح'), + (0x1EEA8, 'M', u'ط'), + (0x1EEA9, 'M', u'ي'), + (0x1EEAA, 'X'), + (0x1EEAB, 'M', u'ل'), + (0x1EEAC, 'M', u'م'), + (0x1EEAD, 'M', u'ن'), + (0x1EEAE, 'M', u'س'), + (0x1EEAF, 'M', u'ع'), + (0x1EEB0, 'M', u'ف'), + (0x1EEB1, 'M', u'ص'), + (0x1EEB2, 'M', u'ق'), + (0x1EEB3, 'M', u'ر'), + (0x1EEB4, 'M', u'ش'), + (0x1EEB5, 'M', u'ت'), + (0x1EEB6, 'M', u'ث'), + (0x1EEB7, 'M', u'خ'), + (0x1EEB8, 'M', u'ذ'), + (0x1EEB9, 'M', u'ض'), + (0x1EEBA, 'M', u'ظ'), + (0x1EEBB, 'M', u'غ'), + (0x1EEBC, 'X'), + (0x1EEF0, 'V'), + (0x1EEF2, 'X'), + (0x1F000, 'V'), + (0x1F02C, 'X'), + (0x1F030, 'V'), + (0x1F094, 'X'), + (0x1F0A0, 'V'), + (0x1F0AF, 'X'), + (0x1F0B1, 'V'), + (0x1F0C0, 'X'), + (0x1F0C1, 'V'), + (0x1F0D0, 'X'), + (0x1F0D1, 'V'), + (0x1F0F6, 'X'), + (0x1F101, '3', u'0,'), + (0x1F102, '3', u'1,'), + (0x1F103, '3', u'2,'), + (0x1F104, '3', u'3,'), + (0x1F105, '3', u'4,'), + (0x1F106, '3', u'5,'), + (0x1F107, '3', u'6,'), + (0x1F108, '3', u'7,'), + (0x1F109, '3', u'8,'), + (0x1F10A, '3', u'9,'), + (0x1F10B, 'V'), + (0x1F10D, 'X'), + (0x1F110, '3', u'(a)'), + (0x1F111, '3', u'(b)'), + (0x1F112, '3', u'(c)'), + (0x1F113, '3', u'(d)'), + (0x1F114, '3', u'(e)'), + (0x1F115, '3', u'(f)'), + (0x1F116, '3', u'(g)'), + (0x1F117, '3', u'(h)'), + (0x1F118, '3', u'(i)'), + (0x1F119, '3', u'(j)'), + (0x1F11A, '3', u'(k)'), + (0x1F11B, '3', u'(l)'), + (0x1F11C, '3', u'(m)'), + (0x1F11D, '3', u'(n)'), + (0x1F11E, '3', u'(o)'), + (0x1F11F, '3', u'(p)'), + (0x1F120, '3', u'(q)'), + (0x1F121, '3', u'(r)'), + (0x1F122, '3', u'(s)'), + (0x1F123, '3', u'(t)'), + (0x1F124, '3', u'(u)'), + ] + +def _seg_71(): + return [ + (0x1F125, '3', u'(v)'), + (0x1F126, '3', u'(w)'), + (0x1F127, '3', u'(x)'), + (0x1F128, '3', u'(y)'), + (0x1F129, '3', u'(z)'), + (0x1F12A, 'M', u'〔s〕'), + (0x1F12B, 'M', u'c'), + (0x1F12C, 'M', u'r'), + (0x1F12D, 'M', u'cd'), + (0x1F12E, 'M', u'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', u'a'), + (0x1F131, 'M', u'b'), + (0x1F132, 'M', u'c'), + (0x1F133, 'M', u'd'), + (0x1F134, 'M', u'e'), + (0x1F135, 'M', u'f'), + (0x1F136, 'M', u'g'), + (0x1F137, 'M', u'h'), + (0x1F138, 'M', u'i'), + (0x1F139, 'M', u'j'), + (0x1F13A, 'M', u'k'), + (0x1F13B, 'M', u'l'), + (0x1F13C, 'M', u'm'), + (0x1F13D, 'M', u'n'), + (0x1F13E, 'M', u'o'), + (0x1F13F, 'M', u'p'), + (0x1F140, 'M', u'q'), + (0x1F141, 'M', u'r'), + (0x1F142, 'M', u's'), + (0x1F143, 'M', u't'), + (0x1F144, 'M', u'u'), + (0x1F145, 'M', u'v'), + (0x1F146, 'M', u'w'), + (0x1F147, 'M', u'x'), + (0x1F148, 'M', u'y'), + (0x1F149, 'M', u'z'), + (0x1F14A, 'M', u'hv'), + (0x1F14B, 'M', u'mv'), + (0x1F14C, 'M', u'sd'), + (0x1F14D, 'M', u'ss'), + (0x1F14E, 'M', u'ppv'), + (0x1F14F, 'M', u'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', u'mc'), + (0x1F16B, 'M', u'md'), + (0x1F16C, 'X'), + (0x1F170, 'V'), + (0x1F190, 'M', u'dj'), + (0x1F191, 'V'), + (0x1F1AD, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', u'ほか'), + (0x1F201, 'M', u'ココ'), + (0x1F202, 'M', u'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', u'手'), + (0x1F211, 'M', u'字'), + (0x1F212, 'M', u'双'), + (0x1F213, 'M', u'デ'), + (0x1F214, 'M', u'二'), + (0x1F215, 'M', u'多'), + (0x1F216, 'M', u'解'), + (0x1F217, 'M', u'天'), + (0x1F218, 'M', u'交'), + (0x1F219, 'M', u'映'), + (0x1F21A, 'M', u'無'), + (0x1F21B, 'M', u'料'), + (0x1F21C, 'M', u'前'), + (0x1F21D, 'M', u'後'), + (0x1F21E, 'M', u'再'), + (0x1F21F, 'M', u'新'), + (0x1F220, 'M', u'初'), + (0x1F221, 'M', u'終'), + (0x1F222, 'M', u'生'), + (0x1F223, 'M', u'販'), + (0x1F224, 'M', u'声'), + (0x1F225, 'M', u'吹'), + (0x1F226, 'M', u'演'), + (0x1F227, 'M', u'投'), + (0x1F228, 'M', u'捕'), + (0x1F229, 'M', u'一'), + (0x1F22A, 'M', u'三'), + (0x1F22B, 'M', u'遊'), + (0x1F22C, 'M', u'左'), + (0x1F22D, 'M', u'中'), + (0x1F22E, 'M', u'右'), + (0x1F22F, 'M', u'指'), + (0x1F230, 'M', u'走'), + (0x1F231, 'M', u'打'), + (0x1F232, 'M', u'禁'), + (0x1F233, 'M', u'空'), + (0x1F234, 'M', u'合'), + (0x1F235, 'M', u'満'), + (0x1F236, 'M', u'有'), + (0x1F237, 'M', u'月'), + (0x1F238, 'M', u'申'), + (0x1F239, 'M', u'割'), + (0x1F23A, 'M', u'営'), + (0x1F23B, 'M', u'配'), + ] + +def _seg_72(): + return [ + (0x1F23C, 'X'), + (0x1F240, 'M', u'〔本〕'), + (0x1F241, 'M', u'〔三〕'), + (0x1F242, 'M', u'〔二〕'), + (0x1F243, 'M', u'〔安〕'), + (0x1F244, 'M', u'〔点〕'), + (0x1F245, 'M', u'〔打〕'), + (0x1F246, 'M', u'〔盗〕'), + (0x1F247, 'M', u'〔勝〕'), + (0x1F248, 'M', u'〔敗〕'), + (0x1F249, 'X'), + (0x1F250, 'M', u'得'), + (0x1F251, 'M', u'可'), + (0x1F252, 'X'), + (0x1F260, 'V'), + (0x1F266, 'X'), + (0x1F300, 'V'), + (0x1F6D5, 'X'), + (0x1F6E0, 'V'), + (0x1F6ED, 'X'), + (0x1F6F0, 'V'), + (0x1F6FA, 'X'), + (0x1F700, 'V'), + (0x1F774, 'X'), + (0x1F780, 'V'), + (0x1F7D9, 'X'), + (0x1F800, 'V'), + (0x1F80C, 'X'), + (0x1F810, 'V'), + (0x1F848, 'X'), + (0x1F850, 'V'), + (0x1F85A, 'X'), + (0x1F860, 'V'), + (0x1F888, 'X'), + (0x1F890, 'V'), + (0x1F8AE, 'X'), + (0x1F900, 'V'), + (0x1F90C, 'X'), + (0x1F910, 'V'), + (0x1F93F, 'X'), + (0x1F940, 'V'), + (0x1F971, 'X'), + (0x1F973, 'V'), + (0x1F977, 'X'), + (0x1F97A, 'V'), + (0x1F97B, 'X'), + (0x1F97C, 'V'), + (0x1F9A3, 'X'), + (0x1F9B0, 'V'), + (0x1F9BA, 'X'), + (0x1F9C0, 'V'), + (0x1F9C3, 'X'), + (0x1F9D0, 'V'), + (0x1FA00, 'X'), + (0x1FA60, 'V'), + (0x1FA6E, 'X'), + (0x20000, 'V'), + (0x2A6D7, 'X'), + (0x2A700, 'V'), + (0x2B735, 'X'), + (0x2B740, 'V'), + (0x2B81E, 'X'), + (0x2B820, 'V'), + (0x2CEA2, 'X'), + (0x2CEB0, 'V'), + (0x2EBE1, 'X'), + (0x2F800, 'M', u'丽'), + (0x2F801, 'M', u'丸'), + (0x2F802, 'M', u'乁'), + (0x2F803, 'M', u'𠄢'), + (0x2F804, 'M', u'你'), + (0x2F805, 'M', u'侮'), + (0x2F806, 'M', u'侻'), + (0x2F807, 'M', u'倂'), + (0x2F808, 'M', u'偺'), + (0x2F809, 'M', u'備'), + (0x2F80A, 'M', u'僧'), + (0x2F80B, 'M', u'像'), + (0x2F80C, 'M', u'㒞'), + (0x2F80D, 'M', u'𠘺'), + (0x2F80E, 'M', u'免'), + (0x2F80F, 'M', u'兔'), + (0x2F810, 'M', u'兤'), + (0x2F811, 'M', u'具'), + (0x2F812, 'M', u'𠔜'), + (0x2F813, 'M', u'㒹'), + (0x2F814, 'M', u'內'), + (0x2F815, 'M', u'再'), + (0x2F816, 'M', u'𠕋'), + (0x2F817, 'M', u'冗'), + (0x2F818, 'M', u'冤'), + (0x2F819, 'M', u'仌'), + (0x2F81A, 'M', u'冬'), + (0x2F81B, 'M', u'况'), + (0x2F81C, 'M', u'𩇟'), + (0x2F81D, 'M', u'凵'), + (0x2F81E, 'M', u'刃'), + (0x2F81F, 'M', u'㓟'), + (0x2F820, 'M', u'刻'), + (0x2F821, 'M', u'剆'), + ] + +def _seg_73(): + return [ + (0x2F822, 'M', u'割'), + (0x2F823, 'M', u'剷'), + (0x2F824, 'M', u'㔕'), + (0x2F825, 'M', u'勇'), + (0x2F826, 'M', u'勉'), + (0x2F827, 'M', u'勤'), + (0x2F828, 'M', u'勺'), + (0x2F829, 'M', u'包'), + (0x2F82A, 'M', u'匆'), + (0x2F82B, 'M', u'北'), + (0x2F82C, 'M', u'卉'), + (0x2F82D, 'M', u'卑'), + (0x2F82E, 'M', u'博'), + (0x2F82F, 'M', u'即'), + (0x2F830, 'M', u'卽'), + (0x2F831, 'M', u'卿'), + (0x2F834, 'M', u'𠨬'), + (0x2F835, 'M', u'灰'), + (0x2F836, 'M', u'及'), + (0x2F837, 'M', u'叟'), + (0x2F838, 'M', u'𠭣'), + (0x2F839, 'M', u'叫'), + (0x2F83A, 'M', u'叱'), + (0x2F83B, 'M', u'吆'), + (0x2F83C, 'M', u'咞'), + (0x2F83D, 'M', u'吸'), + (0x2F83E, 'M', u'呈'), + (0x2F83F, 'M', u'周'), + (0x2F840, 'M', u'咢'), + (0x2F841, 'M', u'哶'), + (0x2F842, 'M', u'唐'), + (0x2F843, 'M', u'啓'), + (0x2F844, 'M', u'啣'), + (0x2F845, 'M', u'善'), + (0x2F847, 'M', u'喙'), + (0x2F848, 'M', u'喫'), + (0x2F849, 'M', u'喳'), + (0x2F84A, 'M', u'嗂'), + (0x2F84B, 'M', u'圖'), + (0x2F84C, 'M', u'嘆'), + (0x2F84D, 'M', u'圗'), + (0x2F84E, 'M', u'噑'), + (0x2F84F, 'M', u'噴'), + (0x2F850, 'M', u'切'), + (0x2F851, 'M', u'壮'), + (0x2F852, 'M', u'城'), + (0x2F853, 'M', u'埴'), + (0x2F854, 'M', u'堍'), + (0x2F855, 'M', u'型'), + (0x2F856, 'M', u'堲'), + (0x2F857, 'M', u'報'), + (0x2F858, 'M', u'墬'), + (0x2F859, 'M', u'𡓤'), + (0x2F85A, 'M', u'売'), + (0x2F85B, 'M', u'壷'), + (0x2F85C, 'M', u'夆'), + (0x2F85D, 'M', u'多'), + (0x2F85E, 'M', u'夢'), + (0x2F85F, 'M', u'奢'), + (0x2F860, 'M', u'𡚨'), + (0x2F861, 'M', u'𡛪'), + (0x2F862, 'M', u'姬'), + (0x2F863, 'M', u'娛'), + (0x2F864, 'M', u'娧'), + (0x2F865, 'M', u'姘'), + (0x2F866, 'M', u'婦'), + (0x2F867, 'M', u'㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', u'嬈'), + (0x2F86A, 'M', u'嬾'), + (0x2F86C, 'M', u'𡧈'), + (0x2F86D, 'M', u'寃'), + (0x2F86E, 'M', u'寘'), + (0x2F86F, 'M', u'寧'), + (0x2F870, 'M', u'寳'), + (0x2F871, 'M', u'𡬘'), + (0x2F872, 'M', u'寿'), + (0x2F873, 'M', u'将'), + (0x2F874, 'X'), + (0x2F875, 'M', u'尢'), + (0x2F876, 'M', u'㞁'), + (0x2F877, 'M', u'屠'), + (0x2F878, 'M', u'屮'), + (0x2F879, 'M', u'峀'), + (0x2F87A, 'M', u'岍'), + (0x2F87B, 'M', u'𡷤'), + (0x2F87C, 'M', u'嵃'), + (0x2F87D, 'M', u'𡷦'), + (0x2F87E, 'M', u'嵮'), + (0x2F87F, 'M', u'嵫'), + (0x2F880, 'M', u'嵼'), + (0x2F881, 'M', u'巡'), + (0x2F882, 'M', u'巢'), + (0x2F883, 'M', u'㠯'), + (0x2F884, 'M', u'巽'), + (0x2F885, 'M', u'帨'), + (0x2F886, 'M', u'帽'), + (0x2F887, 'M', u'幩'), + (0x2F888, 'M', u'㡢'), + (0x2F889, 'M', u'𢆃'), + ] + +def _seg_74(): + return [ + (0x2F88A, 'M', u'㡼'), + (0x2F88B, 'M', u'庰'), + (0x2F88C, 'M', u'庳'), + (0x2F88D, 'M', u'庶'), + (0x2F88E, 'M', u'廊'), + (0x2F88F, 'M', u'𪎒'), + (0x2F890, 'M', u'廾'), + (0x2F891, 'M', u'𢌱'), + (0x2F893, 'M', u'舁'), + (0x2F894, 'M', u'弢'), + (0x2F896, 'M', u'㣇'), + (0x2F897, 'M', u'𣊸'), + (0x2F898, 'M', u'𦇚'), + (0x2F899, 'M', u'形'), + (0x2F89A, 'M', u'彫'), + (0x2F89B, 'M', u'㣣'), + (0x2F89C, 'M', u'徚'), + (0x2F89D, 'M', u'忍'), + (0x2F89E, 'M', u'志'), + (0x2F89F, 'M', u'忹'), + (0x2F8A0, 'M', u'悁'), + (0x2F8A1, 'M', u'㤺'), + (0x2F8A2, 'M', u'㤜'), + (0x2F8A3, 'M', u'悔'), + (0x2F8A4, 'M', u'𢛔'), + (0x2F8A5, 'M', u'惇'), + (0x2F8A6, 'M', u'慈'), + (0x2F8A7, 'M', u'慌'), + (0x2F8A8, 'M', u'慎'), + (0x2F8A9, 'M', u'慌'), + (0x2F8AA, 'M', u'慺'), + (0x2F8AB, 'M', u'憎'), + (0x2F8AC, 'M', u'憲'), + (0x2F8AD, 'M', u'憤'), + (0x2F8AE, 'M', u'憯'), + (0x2F8AF, 'M', u'懞'), + (0x2F8B0, 'M', u'懲'), + (0x2F8B1, 'M', u'懶'), + (0x2F8B2, 'M', u'成'), + (0x2F8B3, 'M', u'戛'), + (0x2F8B4, 'M', u'扝'), + (0x2F8B5, 'M', u'抱'), + (0x2F8B6, 'M', u'拔'), + (0x2F8B7, 'M', u'捐'), + (0x2F8B8, 'M', u'𢬌'), + (0x2F8B9, 'M', u'挽'), + (0x2F8BA, 'M', u'拼'), + (0x2F8BB, 'M', u'捨'), + (0x2F8BC, 'M', u'掃'), + (0x2F8BD, 'M', u'揤'), + (0x2F8BE, 'M', u'𢯱'), + (0x2F8BF, 'M', u'搢'), + (0x2F8C0, 'M', u'揅'), + (0x2F8C1, 'M', u'掩'), + (0x2F8C2, 'M', u'㨮'), + (0x2F8C3, 'M', u'摩'), + (0x2F8C4, 'M', u'摾'), + (0x2F8C5, 'M', u'撝'), + (0x2F8C6, 'M', u'摷'), + (0x2F8C7, 'M', u'㩬'), + (0x2F8C8, 'M', u'敏'), + (0x2F8C9, 'M', u'敬'), + (0x2F8CA, 'M', u'𣀊'), + (0x2F8CB, 'M', u'旣'), + (0x2F8CC, 'M', u'書'), + (0x2F8CD, 'M', u'晉'), + (0x2F8CE, 'M', u'㬙'), + (0x2F8CF, 'M', u'暑'), + (0x2F8D0, 'M', u'㬈'), + (0x2F8D1, 'M', u'㫤'), + (0x2F8D2, 'M', u'冒'), + (0x2F8D3, 'M', u'冕'), + (0x2F8D4, 'M', u'最'), + (0x2F8D5, 'M', u'暜'), + (0x2F8D6, 'M', u'肭'), + (0x2F8D7, 'M', u'䏙'), + (0x2F8D8, 'M', u'朗'), + (0x2F8D9, 'M', u'望'), + (0x2F8DA, 'M', u'朡'), + (0x2F8DB, 'M', u'杞'), + (0x2F8DC, 'M', u'杓'), + (0x2F8DD, 'M', u'𣏃'), + (0x2F8DE, 'M', u'㭉'), + (0x2F8DF, 'M', u'柺'), + (0x2F8E0, 'M', u'枅'), + (0x2F8E1, 'M', u'桒'), + (0x2F8E2, 'M', u'梅'), + (0x2F8E3, 'M', u'𣑭'), + (0x2F8E4, 'M', u'梎'), + (0x2F8E5, 'M', u'栟'), + (0x2F8E6, 'M', u'椔'), + (0x2F8E7, 'M', u'㮝'), + (0x2F8E8, 'M', u'楂'), + (0x2F8E9, 'M', u'榣'), + (0x2F8EA, 'M', u'槪'), + (0x2F8EB, 'M', u'檨'), + (0x2F8EC, 'M', u'𣚣'), + (0x2F8ED, 'M', u'櫛'), + (0x2F8EE, 'M', u'㰘'), + (0x2F8EF, 'M', u'次'), + ] + +def _seg_75(): + return [ + (0x2F8F0, 'M', u'𣢧'), + (0x2F8F1, 'M', u'歔'), + (0x2F8F2, 'M', u'㱎'), + (0x2F8F3, 'M', u'歲'), + (0x2F8F4, 'M', u'殟'), + (0x2F8F5, 'M', u'殺'), + (0x2F8F6, 'M', u'殻'), + (0x2F8F7, 'M', u'𣪍'), + (0x2F8F8, 'M', u'𡴋'), + (0x2F8F9, 'M', u'𣫺'), + (0x2F8FA, 'M', u'汎'), + (0x2F8FB, 'M', u'𣲼'), + (0x2F8FC, 'M', u'沿'), + (0x2F8FD, 'M', u'泍'), + (0x2F8FE, 'M', u'汧'), + (0x2F8FF, 'M', u'洖'), + (0x2F900, 'M', u'派'), + (0x2F901, 'M', u'海'), + (0x2F902, 'M', u'流'), + (0x2F903, 'M', u'浩'), + (0x2F904, 'M', u'浸'), + (0x2F905, 'M', u'涅'), + (0x2F906, 'M', u'𣴞'), + (0x2F907, 'M', u'洴'), + (0x2F908, 'M', u'港'), + (0x2F909, 'M', u'湮'), + (0x2F90A, 'M', u'㴳'), + (0x2F90B, 'M', u'滋'), + (0x2F90C, 'M', u'滇'), + (0x2F90D, 'M', u'𣻑'), + (0x2F90E, 'M', u'淹'), + (0x2F90F, 'M', u'潮'), + (0x2F910, 'M', u'𣽞'), + (0x2F911, 'M', u'𣾎'), + (0x2F912, 'M', u'濆'), + (0x2F913, 'M', u'瀹'), + (0x2F914, 'M', u'瀞'), + (0x2F915, 'M', u'瀛'), + (0x2F916, 'M', u'㶖'), + (0x2F917, 'M', u'灊'), + (0x2F918, 'M', u'災'), + (0x2F919, 'M', u'灷'), + (0x2F91A, 'M', u'炭'), + (0x2F91B, 'M', u'𠔥'), + (0x2F91C, 'M', u'煅'), + (0x2F91D, 'M', u'𤉣'), + (0x2F91E, 'M', u'熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', u'爨'), + (0x2F921, 'M', u'爵'), + (0x2F922, 'M', u'牐'), + (0x2F923, 'M', u'𤘈'), + (0x2F924, 'M', u'犀'), + (0x2F925, 'M', u'犕'), + (0x2F926, 'M', u'𤜵'), + (0x2F927, 'M', u'𤠔'), + (0x2F928, 'M', u'獺'), + (0x2F929, 'M', u'王'), + (0x2F92A, 'M', u'㺬'), + (0x2F92B, 'M', u'玥'), + (0x2F92C, 'M', u'㺸'), + (0x2F92E, 'M', u'瑇'), + (0x2F92F, 'M', u'瑜'), + (0x2F930, 'M', u'瑱'), + (0x2F931, 'M', u'璅'), + (0x2F932, 'M', u'瓊'), + (0x2F933, 'M', u'㼛'), + (0x2F934, 'M', u'甤'), + (0x2F935, 'M', u'𤰶'), + (0x2F936, 'M', u'甾'), + (0x2F937, 'M', u'𤲒'), + (0x2F938, 'M', u'異'), + (0x2F939, 'M', u'𢆟'), + (0x2F93A, 'M', u'瘐'), + (0x2F93B, 'M', u'𤾡'), + (0x2F93C, 'M', u'𤾸'), + (0x2F93D, 'M', u'𥁄'), + (0x2F93E, 'M', u'㿼'), + (0x2F93F, 'M', u'䀈'), + (0x2F940, 'M', u'直'), + (0x2F941, 'M', u'𥃳'), + (0x2F942, 'M', u'𥃲'), + (0x2F943, 'M', u'𥄙'), + (0x2F944, 'M', u'𥄳'), + (0x2F945, 'M', u'眞'), + (0x2F946, 'M', u'真'), + (0x2F948, 'M', u'睊'), + (0x2F949, 'M', u'䀹'), + (0x2F94A, 'M', u'瞋'), + (0x2F94B, 'M', u'䁆'), + (0x2F94C, 'M', u'䂖'), + (0x2F94D, 'M', u'𥐝'), + (0x2F94E, 'M', u'硎'), + (0x2F94F, 'M', u'碌'), + (0x2F950, 'M', u'磌'), + (0x2F951, 'M', u'䃣'), + (0x2F952, 'M', u'𥘦'), + (0x2F953, 'M', u'祖'), + (0x2F954, 'M', u'𥚚'), + (0x2F955, 'M', u'𥛅'), + ] + +def _seg_76(): + return [ + (0x2F956, 'M', u'福'), + (0x2F957, 'M', u'秫'), + (0x2F958, 'M', u'䄯'), + (0x2F959, 'M', u'穀'), + (0x2F95A, 'M', u'穊'), + (0x2F95B, 'M', u'穏'), + (0x2F95C, 'M', u'𥥼'), + (0x2F95D, 'M', u'𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', u'䈂'), + (0x2F961, 'M', u'𥮫'), + (0x2F962, 'M', u'篆'), + (0x2F963, 'M', u'築'), + (0x2F964, 'M', u'䈧'), + (0x2F965, 'M', u'𥲀'), + (0x2F966, 'M', u'糒'), + (0x2F967, 'M', u'䊠'), + (0x2F968, 'M', u'糨'), + (0x2F969, 'M', u'糣'), + (0x2F96A, 'M', u'紀'), + (0x2F96B, 'M', u'𥾆'), + (0x2F96C, 'M', u'絣'), + (0x2F96D, 'M', u'䌁'), + (0x2F96E, 'M', u'緇'), + (0x2F96F, 'M', u'縂'), + (0x2F970, 'M', u'繅'), + (0x2F971, 'M', u'䌴'), + (0x2F972, 'M', u'𦈨'), + (0x2F973, 'M', u'𦉇'), + (0x2F974, 'M', u'䍙'), + (0x2F975, 'M', u'𦋙'), + (0x2F976, 'M', u'罺'), + (0x2F977, 'M', u'𦌾'), + (0x2F978, 'M', u'羕'), + (0x2F979, 'M', u'翺'), + (0x2F97A, 'M', u'者'), + (0x2F97B, 'M', u'𦓚'), + (0x2F97C, 'M', u'𦔣'), + (0x2F97D, 'M', u'聠'), + (0x2F97E, 'M', u'𦖨'), + (0x2F97F, 'M', u'聰'), + (0x2F980, 'M', u'𣍟'), + (0x2F981, 'M', u'䏕'), + (0x2F982, 'M', u'育'), + (0x2F983, 'M', u'脃'), + (0x2F984, 'M', u'䐋'), + (0x2F985, 'M', u'脾'), + (0x2F986, 'M', u'媵'), + (0x2F987, 'M', u'𦞧'), + (0x2F988, 'M', u'𦞵'), + (0x2F989, 'M', u'𣎓'), + (0x2F98A, 'M', u'𣎜'), + (0x2F98B, 'M', u'舁'), + (0x2F98C, 'M', u'舄'), + (0x2F98D, 'M', u'辞'), + (0x2F98E, 'M', u'䑫'), + (0x2F98F, 'M', u'芑'), + (0x2F990, 'M', u'芋'), + (0x2F991, 'M', u'芝'), + (0x2F992, 'M', u'劳'), + (0x2F993, 'M', u'花'), + (0x2F994, 'M', u'芳'), + (0x2F995, 'M', u'芽'), + (0x2F996, 'M', u'苦'), + (0x2F997, 'M', u'𦬼'), + (0x2F998, 'M', u'若'), + (0x2F999, 'M', u'茝'), + (0x2F99A, 'M', u'荣'), + (0x2F99B, 'M', u'莭'), + (0x2F99C, 'M', u'茣'), + (0x2F99D, 'M', u'莽'), + (0x2F99E, 'M', u'菧'), + (0x2F99F, 'M', u'著'), + (0x2F9A0, 'M', u'荓'), + (0x2F9A1, 'M', u'菊'), + (0x2F9A2, 'M', u'菌'), + (0x2F9A3, 'M', u'菜'), + (0x2F9A4, 'M', u'𦰶'), + (0x2F9A5, 'M', u'𦵫'), + (0x2F9A6, 'M', u'𦳕'), + (0x2F9A7, 'M', u'䔫'), + (0x2F9A8, 'M', u'蓱'), + (0x2F9A9, 'M', u'蓳'), + (0x2F9AA, 'M', u'蔖'), + (0x2F9AB, 'M', u'𧏊'), + (0x2F9AC, 'M', u'蕤'), + (0x2F9AD, 'M', u'𦼬'), + (0x2F9AE, 'M', u'䕝'), + (0x2F9AF, 'M', u'䕡'), + (0x2F9B0, 'M', u'𦾱'), + (0x2F9B1, 'M', u'𧃒'), + (0x2F9B2, 'M', u'䕫'), + (0x2F9B3, 'M', u'虐'), + (0x2F9B4, 'M', u'虜'), + (0x2F9B5, 'M', u'虧'), + (0x2F9B6, 'M', u'虩'), + (0x2F9B7, 'M', u'蚩'), + (0x2F9B8, 'M', u'蚈'), + (0x2F9B9, 'M', u'蜎'), + (0x2F9BA, 'M', u'蛢'), + ] + +def _seg_77(): + return [ + (0x2F9BB, 'M', u'蝹'), + (0x2F9BC, 'M', u'蜨'), + (0x2F9BD, 'M', u'蝫'), + (0x2F9BE, 'M', u'螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', u'蟡'), + (0x2F9C1, 'M', u'蠁'), + (0x2F9C2, 'M', u'䗹'), + (0x2F9C3, 'M', u'衠'), + (0x2F9C4, 'M', u'衣'), + (0x2F9C5, 'M', u'𧙧'), + (0x2F9C6, 'M', u'裗'), + (0x2F9C7, 'M', u'裞'), + (0x2F9C8, 'M', u'䘵'), + (0x2F9C9, 'M', u'裺'), + (0x2F9CA, 'M', u'㒻'), + (0x2F9CB, 'M', u'𧢮'), + (0x2F9CC, 'M', u'𧥦'), + (0x2F9CD, 'M', u'䚾'), + (0x2F9CE, 'M', u'䛇'), + (0x2F9CF, 'M', u'誠'), + (0x2F9D0, 'M', u'諭'), + (0x2F9D1, 'M', u'變'), + (0x2F9D2, 'M', u'豕'), + (0x2F9D3, 'M', u'𧲨'), + (0x2F9D4, 'M', u'貫'), + (0x2F9D5, 'M', u'賁'), + (0x2F9D6, 'M', u'贛'), + (0x2F9D7, 'M', u'起'), + (0x2F9D8, 'M', u'𧼯'), + (0x2F9D9, 'M', u'𠠄'), + (0x2F9DA, 'M', u'跋'), + (0x2F9DB, 'M', u'趼'), + (0x2F9DC, 'M', u'跰'), + (0x2F9DD, 'M', u'𠣞'), + (0x2F9DE, 'M', u'軔'), + (0x2F9DF, 'M', u'輸'), + (0x2F9E0, 'M', u'𨗒'), + (0x2F9E1, 'M', u'𨗭'), + (0x2F9E2, 'M', u'邔'), + (0x2F9E3, 'M', u'郱'), + (0x2F9E4, 'M', u'鄑'), + (0x2F9E5, 'M', u'𨜮'), + (0x2F9E6, 'M', u'鄛'), + (0x2F9E7, 'M', u'鈸'), + (0x2F9E8, 'M', u'鋗'), + (0x2F9E9, 'M', u'鋘'), + (0x2F9EA, 'M', u'鉼'), + (0x2F9EB, 'M', u'鏹'), + (0x2F9EC, 'M', u'鐕'), + (0x2F9ED, 'M', u'𨯺'), + (0x2F9EE, 'M', u'開'), + (0x2F9EF, 'M', u'䦕'), + (0x2F9F0, 'M', u'閷'), + (0x2F9F1, 'M', u'𨵷'), + (0x2F9F2, 'M', u'䧦'), + (0x2F9F3, 'M', u'雃'), + (0x2F9F4, 'M', u'嶲'), + (0x2F9F5, 'M', u'霣'), + (0x2F9F6, 'M', u'𩅅'), + (0x2F9F7, 'M', u'𩈚'), + (0x2F9F8, 'M', u'䩮'), + (0x2F9F9, 'M', u'䩶'), + (0x2F9FA, 'M', u'韠'), + (0x2F9FB, 'M', u'𩐊'), + (0x2F9FC, 'M', u'䪲'), + (0x2F9FD, 'M', u'𩒖'), + (0x2F9FE, 'M', u'頋'), + (0x2FA00, 'M', u'頩'), + (0x2FA01, 'M', u'𩖶'), + (0x2FA02, 'M', u'飢'), + (0x2FA03, 'M', u'䬳'), + (0x2FA04, 'M', u'餩'), + (0x2FA05, 'M', u'馧'), + (0x2FA06, 'M', u'駂'), + (0x2FA07, 'M', u'駾'), + (0x2FA08, 'M', u'䯎'), + (0x2FA09, 'M', u'𩬰'), + (0x2FA0A, 'M', u'鬒'), + (0x2FA0B, 'M', u'鱀'), + (0x2FA0C, 'M', u'鳽'), + (0x2FA0D, 'M', u'䳎'), + (0x2FA0E, 'M', u'䳭'), + (0x2FA0F, 'M', u'鵧'), + (0x2FA10, 'M', u'𪃎'), + (0x2FA11, 'M', u'䳸'), + (0x2FA12, 'M', u'𪄅'), + (0x2FA13, 'M', u'𪈎'), + (0x2FA14, 'M', u'𪊑'), + (0x2FA15, 'M', u'麻'), + (0x2FA16, 'M', u'䵖'), + (0x2FA17, 'M', u'黹'), + (0x2FA18, 'M', u'黾'), + (0x2FA19, 'M', u'鼅'), + (0x2FA1A, 'M', u'鼏'), + (0x2FA1B, 'M', u'鼖'), + (0x2FA1C, 'M', u'鼻'), + (0x2FA1D, 'M', u'𪘀'), + (0x2FA1E, 'X'), + (0xE0100, 'I'), + ] + +def _seg_78(): + return [ + (0xE01F0, 'X'), + ] + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() +) diff --git a/venv/lib/python3.7/site-packages/mopidy/__init__.py b/venv/lib/python3.7/site-packages/mopidy/__init__.py new file mode 100644 index 0000000..ccb8082 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/__init__.py @@ -0,0 +1,15 @@ +import platform +import sys +import warnings + +import pkg_resources + +if not sys.version_info >= (3, 7): + sys.exit( + f"ERROR: Mopidy requires Python >= 3.7, " + f"but found {platform.python_version()}." + ) + +warnings.filterwarnings("ignore", "could not open display") + +__version__ = pkg_resources.get_distribution("Mopidy").version diff --git a/venv/lib/python3.7/site-packages/mopidy/__main__.py b/venv/lib/python3.7/site-packages/mopidy/__main__.py new file mode 100644 index 0000000..6ef2d2b --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/__main__.py @@ -0,0 +1,231 @@ +import logging +import signal +import sys + +import pykka.debug + +from mopidy import commands +from mopidy import config as config_lib +from mopidy import ext +from mopidy.internal import log, path, process, versioning +from mopidy.internal.gi import Gst # noqa: F401 + +try: + # Make GLib's mainloop the event loop for python-dbus + import dbus.mainloop.glib + + dbus.mainloop.glib.threads_init() + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +except ImportError: + pass + + +logger = logging.getLogger(__name__) + + +def main(): + log.bootstrap_delayed_logging() + logger.info(f"Starting Mopidy {versioning.get_version()}") + + signal.signal(signal.SIGTERM, process.sigterm_handler) + # Windows does not have signal.SIGUSR1 + if hasattr(signal, "SIGUSR1"): + signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks) + + try: + registry = ext.Registry() + + root_cmd = commands.RootCommand() + config_cmd = commands.ConfigCommand() + deps_cmd = commands.DepsCommand() + + root_cmd.set(extension=None, registry=registry) + root_cmd.add_child("config", config_cmd) + root_cmd.add_child("deps", deps_cmd) + + extensions_data = ext.load_extensions() + + for data in extensions_data: + if data.command: # TODO: check isinstance? + data.command.set(extension=data.extension) + root_cmd.add_child(data.extension.ext_name, data.command) + + args = root_cmd.parse(sys.argv[1:]) + + config, config_errors = config_lib.load( + args.config_files, + [d.config_schema for d in extensions_data], + [d.config_defaults for d in extensions_data], + args.config_overrides, + ) + + create_core_dirs(config) + create_initial_config_file(args, extensions_data) + + log.setup_logging( + config, args.base_verbosity_level, args.verbosity_level + ) + + extensions = { + "validate": [], + "config": [], + "disabled": [], + "enabled": [], + } + for data in extensions_data: + extension = data.extension + + # TODO: factor out all of this to a helper that can be tested + if not ext.validate_extension_data(data): + config[extension.ext_name] = {"enabled": False} + config_errors[extension.ext_name] = { + "enabled": "extension disabled by self check." + } + extensions["validate"].append(extension) + elif not config[extension.ext_name]["enabled"]: + config[extension.ext_name] = {"enabled": False} + config_errors[extension.ext_name] = { + "enabled": "extension disabled by user config." + } + extensions["disabled"].append(extension) + elif config_errors.get(extension.ext_name): + config[extension.ext_name]["enabled"] = False + config_errors[extension.ext_name][ + "enabled" + ] = "extension disabled due to config errors." + extensions["config"].append(extension) + else: + extensions["enabled"].append(extension) + + log_extension_info( + [d.extension for d in extensions_data], extensions["enabled"] + ) + + # Config and deps commands are simply special cased for now. + if args.command == config_cmd: + schemas = [d.config_schema for d in extensions_data] + return args.command.run(config, config_errors, schemas) + elif args.command == deps_cmd: + return args.command.run() + + check_config_errors(config, config_errors, extensions) + + if not extensions["enabled"]: + logger.error("No extension enabled, exiting...") + sys.exit(1) + + # Read-only config from here on, please. + proxied_config = config_lib.Proxy(config) + + if args.extension and args.extension not in extensions["enabled"]: + logger.error( + "Unable to run command provided by disabled extension %s", + args.extension.ext_name, + ) + return 1 + + for extension in extensions["enabled"]: + try: + extension.setup(registry) + except Exception: + # TODO: would be nice a transactional registry. But sadly this + # is a bit tricky since our current API is giving out a mutable + # list. We might however be able to replace this with a + # collections.Sequence to provide a RO view. + logger.exception( + f"Extension {extension.ext_name} failed during setup. " + f"This might have left the registry in a bad state." + ) + + # Anything that wants to exit after this point must use + # mopidy.internal.process.exit_process as actors can have been started. + try: + return args.command.run(args, proxied_config) + except NotImplementedError: + print(root_cmd.format_help()) + return 1 + + except KeyboardInterrupt: + pass + except Exception as ex: + logger.exception(ex) + raise + + +def create_core_dirs(config): + path.get_or_create_dir(config["core"]["cache_dir"]) + path.get_or_create_dir(config["core"]["config_dir"]) + path.get_or_create_dir(config["core"]["data_dir"]) + + +def create_initial_config_file(args, extensions_data): + """Initialize whatever the last config file is with defaults""" + + config_file = path.expand_path(args.config_files[-1]) + + if config_file.exists(): + return + + try: + default = config_lib.format_initial(extensions_data) + path.get_or_create_file( + config_file, + mkdir=False, + content=default.encode(errors="surrogateescape"), + ) + logger.info(f"Initialized {config_file.as_uri()} with default config") + except OSError as exc: + logger.warning( + f"Unable to initialize {config_file.as_uri()} with default config: {exc}" + ) + + +def log_extension_info(all_extensions, enabled_extensions): + # TODO: distinguish disabled vs blocked by env? + enabled_names = {e.ext_name for e in enabled_extensions} + disabled_names = {e.ext_name for e in all_extensions} - enabled_names + logger.info("Enabled extensions: %s", ", ".join(enabled_names) or "none") + logger.info("Disabled extensions: %s", ", ".join(disabled_names) or "none") + + +def check_config_errors(config, errors, extensions): + fatal_errors = [] + extension_names = {} + all_extension_names = set() + + for state in extensions: + extension_names[state] = {e.ext_name for e in extensions[state]} + all_extension_names.update(extension_names[state]) + + for section in sorted(errors): + if not errors[section]: + continue + + if section not in all_extension_names: + logger.warning(f"Found fatal {section} configuration errors:") + fatal_errors.append(section) + elif section in extension_names["config"]: + del errors[section]["enabled"] + logger.warning( + f"Found {section} configuration errors. " + f"The extension has been automatically disabled:" + ) + else: + continue + + for field, msg in errors[section].items(): + logger.warning(f" {section}/{field} {msg}") + + if extensions["config"]: + logger.warning( + "Please fix the extension configuration errors or " + "disable the extensions to silence these messages." + ) + + if fatal_errors: + logger.error("Please fix fatal configuration errors, exiting...") + sys.exit(1) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/__init__.py b/venv/lib/python3.7/site-packages/mopidy/audio/__init__.py new file mode 100644 index 0000000..8858a0b --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/__init__.py @@ -0,0 +1,10 @@ +# flake8: noqa +from .actor import Audio +from .constants import PlaybackState +from .listener import AudioListener +from .utils import ( + calculate_duration, + create_buffer, + millisecond_to_clocktime, + supported_uri_schemes, +) diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/actor.py b/venv/lib/python3.7/site-packages/mopidy/audio/actor.py new file mode 100644 index 0000000..e4c4d4a --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/actor.py @@ -0,0 +1,855 @@ +import logging +import os +import threading + +import pykka + +from mopidy import exceptions +from mopidy.audio import tags as tags_lib +from mopidy.audio import utils +from mopidy.audio.constants import PlaybackState +from mopidy.audio.listener import AudioListener +from mopidy.internal import process +from mopidy.internal.gi import GLib, GObject, Gst, GstPbutils + +logger = logging.getLogger(__name__) + +# This logger is only meant for debug logging of low level GStreamer info such +# as callbacks, event, messages and direct interaction with GStreamer such as +# set_state() on a pipeline. +gst_logger = logging.getLogger("mopidy.audio.gst") + +_GST_PLAY_FLAGS_AUDIO = 0x02 + +_GST_STATE_MAPPING = { + Gst.State.PLAYING: PlaybackState.PLAYING, + Gst.State.PAUSED: PlaybackState.PAUSED, + Gst.State.NULL: PlaybackState.STOPPED, +} + + +# TODO: expose this as a property on audio? +class _Appsrc: + + """Helper class for dealing with appsrc based playback.""" + + def __init__(self): + self._signals = utils.Signals() + self.reset() + + def reset(self): + """Reset the helper. + + Should be called whenever the source changes and we are not setting up + a new appsrc. + """ + self.prepare(None, None, None, None) + + def prepare(self, caps, need_data, enough_data, seek_data): + """Store info we will need when the appsrc element gets installed.""" + self._signals.clear() + self._source = None + self._caps = caps + self._need_data_callback = need_data + self._seek_data_callback = seek_data + self._enough_data_callback = enough_data + + def configure(self, source): + """Configure the supplied source for use. + + Should be called whenever we get a new appsrc. + """ + source.set_property("caps", self._caps) + source.set_property("format", "time") + source.set_property("stream-type", "seekable") + source.set_property("max-bytes", 1 << 20) # 1MB + source.set_property("min-percent", 50) + + if self._need_data_callback: + self._signals.connect( + source, "need-data", self._on_signal, self._need_data_callback + ) + if self._seek_data_callback: + self._signals.connect( + source, "seek-data", self._on_signal, self._seek_data_callback + ) + if self._enough_data_callback: + self._signals.connect( + source, + "enough-data", + self._on_signal, + None, + self._enough_data_callback, + ) + + self._source = source + + def push(self, buffer_): + if self._source is None: + return False + + if buffer_ is None: + gst_logger.debug("Sending appsrc end-of-stream event.") + result = self._source.emit("end-of-stream") + return result == Gst.FlowReturn.OK + else: + result = self._source.emit("push-buffer", buffer_) + return result == Gst.FlowReturn.OK + + def _on_signal(self, element, clocktime, func): + # This shim is used to ensure we always return true, and also handles + # that not all the callbacks have a time argument. + if clocktime is None: + func() + else: + func(utils.clocktime_to_millisecond(clocktime)) + return True + + +# TODO: expose this as a property on audio when #790 gets further along. +class _Outputs(Gst.Bin): + def __init__(self): + Gst.Bin.__init__(self) + # TODO gst1: Set 'outputs' as the Bin name for easier debugging + + self._tee = Gst.ElementFactory.make("tee") + self.add(self._tee) + + ghost_pad = Gst.GhostPad.new("sink", self._tee.get_static_pad("sink")) + self.add_pad(ghost_pad) + + def add_output(self, description): + # XXX This only works for pipelines not in use until #790 gets done. + try: + output = Gst.parse_bin_from_description( + description, ghost_unlinked_pads=True + ) + except GLib.GError as ex: + logger.error( + 'Failed to create audio output "%s": %s', description, ex + ) + raise exceptions.AudioException(bytes(ex)) + + self._add(output) + logger.info('Audio output set to "%s"', description) + + def _add(self, element): + queue = Gst.ElementFactory.make("queue") + self.add(element) + self.add(queue) + queue.link(element) + self._tee.link(queue) + + +class SoftwareMixer: + def __init__(self, mixer): + self._mixer = mixer + self._element = None + self._last_volume = None + self._last_mute = None + self._signals = utils.Signals() + + def setup(self, element, mixer_ref): + self._element = element + self._mixer.setup(mixer_ref) + + def teardown(self): + self._signals.clear() + self._mixer.teardown() + + def get_volume(self): + return int(round(self._element.get_property("volume") * 100)) + + def set_volume(self, volume): + self._element.set_property("volume", volume / 100.0) + self._mixer.trigger_volume_changed(self.get_volume()) + + def get_mute(self): + return self._element.get_property("mute") + + def set_mute(self, mute): + self._element.set_property("mute", bool(mute)) + self._mixer.trigger_mute_changed(self.get_mute()) + + +class _Handler: + def __init__(self, audio): + self._audio = audio + self._element = None + self._pad = None + self._message_handler_id = None + self._event_handler_id = None + + def setup_message_handling(self, element): + self._element = element + bus = element.get_bus() + bus.add_signal_watch() + self._message_handler_id = bus.connect("message", self.on_message) + + def setup_event_handling(self, pad): + self._pad = pad + self._event_handler_id = pad.add_probe( + Gst.PadProbeType.EVENT_BOTH, self.on_pad_event + ) + + def teardown_message_handling(self): + bus = self._element.get_bus() + bus.remove_signal_watch() + bus.disconnect(self._message_handler_id) + self._message_handler_id = None + + def teardown_event_handling(self): + self._pad.remove_probe(self._event_handler_id) + self._event_handler_id = None + + def on_message(self, bus, msg): + if msg.type == Gst.MessageType.STATE_CHANGED: + if msg.src != self._element: + return + old_state, new_state, pending_state = msg.parse_state_changed() + self.on_playbin_state_changed(old_state, new_state, pending_state) + elif msg.type == Gst.MessageType.BUFFERING: + self.on_buffering(msg.parse_buffering(), msg.get_structure()) + elif msg.type == Gst.MessageType.EOS: + self.on_end_of_stream() + elif msg.type == Gst.MessageType.ERROR: + error, debug = msg.parse_error() + self.on_error(error, debug) + elif msg.type == Gst.MessageType.WARNING: + error, debug = msg.parse_warning() + self.on_warning(error, debug) + elif msg.type == Gst.MessageType.ASYNC_DONE: + self.on_async_done() + elif msg.type == Gst.MessageType.TAG: + taglist = msg.parse_tag() + self.on_tag(taglist) + elif msg.type == Gst.MessageType.ELEMENT: + if GstPbutils.is_missing_plugin_message(msg): + self.on_missing_plugin(msg) + elif msg.type == Gst.MessageType.STREAM_START: + self.on_stream_start() + + def on_pad_event(self, pad, pad_probe_info): + event = pad_probe_info.get_event() + if event.type == Gst.EventType.SEGMENT: + self.on_segment(event.parse_segment()) + return Gst.PadProbeReturn.OK + + def on_playbin_state_changed(self, old_state, new_state, pending_state): + gst_logger.debug( + "Got STATE_CHANGED bus message: old=%s new=%s pending=%s", + old_state.value_name, + new_state.value_name, + pending_state.value_name, + ) + + if new_state == Gst.State.READY and pending_state == Gst.State.NULL: + # XXX: We're not called on the last state change when going down to + # NULL, so we rewrite the second to last call to get the expected + # behavior. + new_state = Gst.State.NULL + pending_state = Gst.State.VOID_PENDING + + if pending_state != Gst.State.VOID_PENDING: + return # Ignore intermediate state changes + + if new_state == Gst.State.READY: + return # Ignore READY state as it's GStreamer specific + + new_state = _GST_STATE_MAPPING[new_state] + old_state, self._audio.state = self._audio.state, new_state + + target_state = _GST_STATE_MAPPING.get(self._audio._target_state) + if target_state is None: + # XXX: Workaround for #1430, to be fixed properly by #1222. + logger.warn("Race condition happened. See #1222 and #1430.") + return + if target_state == new_state: + target_state = None + + logger.debug( + "Audio event: state_changed(old_state=%s, new_state=%s, " + "target_state=%s)", + old_state, + new_state, + target_state, + ) + AudioListener.send( + "state_changed", + old_state=old_state, + new_state=new_state, + target_state=target_state, + ) + if new_state == PlaybackState.STOPPED: + logger.debug("Audio event: stream_changed(uri=None)") + AudioListener.send("stream_changed", uri=None) + + if "GST_DEBUG_DUMP_DOT_DIR" in os.environ: + Gst.debug_bin_to_dot_file( + self._audio._playbin, Gst.DebugGraphDetails.ALL, "mopidy" + ) + + def on_buffering(self, percent, structure=None): + if self._audio._target_state < Gst.State.PAUSED: + gst_logger.debug("Skip buffering during track change.") + return + + if structure is not None and structure.has_field("buffering-mode"): + buffering_mode = structure.get_enum( + "buffering-mode", Gst.BufferingMode + ) + if buffering_mode == Gst.BufferingMode.LIVE: + return # Live sources stall in paused. + + level = logging.getLevelName("TRACE") + if percent < 10 and not self._audio._buffering: + self._audio._playbin.set_state(Gst.State.PAUSED) + self._audio._buffering = True + level = logging.DEBUG + if percent == 100: + self._audio._buffering = False + if self._audio._target_state == Gst.State.PLAYING: + self._audio._playbin.set_state(Gst.State.PLAYING) + level = logging.DEBUG + + gst_logger.log( + level, "Got BUFFERING bus message: percent=%d%%", percent + ) + + def on_end_of_stream(self): + gst_logger.debug("Got EOS (end of stream) bus message.") + logger.debug("Audio event: reached_end_of_stream()") + self._audio._tags = {} + AudioListener.send("reached_end_of_stream") + + def on_error(self, error, debug): + gst_logger.error(f"GStreamer error: {error.message}") + gst_logger.debug( + f"Got ERROR bus message: error={error!r} debug={debug!r}" + ) + + # TODO: is this needed? + self._audio.stop_playback() + + def on_warning(self, error, debug): + gst_logger.warning(f"GStreamer warning: {error.message}") + gst_logger.debug( + f"Got WARNING bus message: error={error!r} debug={debug!r}" + ) + + def on_async_done(self): + gst_logger.debug("Got ASYNC_DONE bus message.") + + def on_tag(self, taglist): + tags = tags_lib.convert_taglist(taglist) + gst_logger.debug("Got TAG bus message: tags=%r", dict(tags)) + + # Postpone emitting tags until stream start. + if self._audio._pending_tags is not None: + self._audio._pending_tags.update(tags) + return + + # TODO: Add proper tests for only emitting changed tags. + unique = object() + changed = [] + for key, value in tags.items(): + # Update any tags that changed, and store changed keys. + if self._audio._tags.get(key, unique) != value: + self._audio._tags[key] = value + changed.append(key) + + if changed: + logger.debug("Audio event: tags_changed(tags=%r)", changed) + AudioListener.send("tags_changed", tags=changed) + + def on_missing_plugin(self, msg): + desc = GstPbutils.missing_plugin_message_get_description(msg) + debug = GstPbutils.missing_plugin_message_get_installer_detail(msg) + gst_logger.debug("Got missing-plugin bus message: description=%r", desc) + logger.warning("Could not find a %s to handle media.", desc) + if GstPbutils.install_plugins_supported(): + logger.info( + "You might be able to fix this by running: " + 'gst-installer "%s"', + debug, + ) + # TODO: store the missing plugins installer info in a file so we can + # can provide a 'mopidy install-missing-plugins' if the system has the + # required helper installed? + + def on_stream_start(self): + gst_logger.debug("Got STREAM_START bus message") + uri = self._audio._pending_uri + logger.debug("Audio event: stream_changed(uri=%r)", uri) + AudioListener.send("stream_changed", uri=uri) + + # Emit any postponed tags that we got after about-to-finish. + tags, self._audio._pending_tags = self._audio._pending_tags, None + self._audio._tags = tags or {} + + if tags: + logger.debug("Audio event: tags_changed(tags=%r)", tags.keys()) + AudioListener.send("tags_changed", tags=tags.keys()) + + if self._audio._pending_metadata: + self._audio._playbin.send_event(self._audio._pending_metadata) + self._audio._pending_metadata = None + + def on_segment(self, segment): + gst_logger.debug( + "Got SEGMENT pad event: " + "rate=%(rate)s format=%(format)s start=%(start)s stop=%(stop)s " + "position=%(position)s", + { + "rate": segment.rate, + "format": Gst.Format.get_name(segment.format), + "start": segment.start, + "stop": segment.stop, + "position": segment.position, + }, + ) + position_ms = segment.position // Gst.MSECOND + logger.debug("Audio event: position_changed(position=%r)", position_ms) + AudioListener.send("position_changed", position=position_ms) + + +# TODO: create a player class which replaces the actors internals +class Audio(pykka.ThreadingActor): + + """ + Audio output through `GStreamer `_. + """ + + #: The GStreamer state mapped to :class:`mopidy.audio.PlaybackState` + state = PlaybackState.STOPPED + + #: The software mixing interface :class:`mopidy.audio.actor.SoftwareMixer` + mixer = None + + def __init__(self, config, mixer): + super().__init__() + + self._config = config + self._target_state = Gst.State.NULL + self._buffering = False + self._live_stream = False + self._tags = {} + self._pending_uri = None + self._pending_tags = None + self._pending_metadata = None + + self._playbin = None + self._outputs = None + self._queue = None + self._about_to_finish_callback = None + + self._handler = _Handler(self) + self._appsrc = _Appsrc() + self._signals = utils.Signals() + + if mixer and self._config["audio"]["mixer"] == "software": + self.mixer = pykka.traversable(SoftwareMixer(mixer)) + + def on_start(self): + self._thread = threading.current_thread() + try: + self._setup_preferences() + self._setup_playbin() + self._setup_outputs() + self._setup_audio_sink() + except GLib.GError as ex: + logger.exception(ex) + process.exit_process() + + def on_stop(self): + self._teardown_mixer() + self._teardown_playbin() + + def _setup_preferences(self): + # TODO: move out of audio actor? + # Fix for https://github.com/mopidy/mopidy/issues/604 + registry = Gst.Registry.get() + jacksink = registry.find_feature("jackaudiosink", Gst.ElementFactory) + if jacksink: + jacksink.set_rank(Gst.Rank.SECONDARY) + + def _setup_playbin(self): + playbin = Gst.ElementFactory.make("playbin") + playbin.set_property("flags", _GST_PLAY_FLAGS_AUDIO) + + # TODO: turn into config values... + playbin.set_property("buffer-size", 5 << 20) # 5MB + playbin.set_property("buffer-duration", 5 * Gst.SECOND) + + self._signals.connect(playbin, "source-setup", self._on_source_setup) + self._signals.connect( + playbin, "about-to-finish", self._on_about_to_finish + ) + + self._playbin = playbin + self._handler.setup_message_handling(playbin) + + def _teardown_playbin(self): + self._handler.teardown_message_handling() + self._handler.teardown_event_handling() + self._signals.disconnect(self._playbin, "about-to-finish") + self._signals.disconnect(self._playbin, "source-setup") + self._playbin.set_state(Gst.State.NULL) + + def _setup_outputs(self): + # We don't want to use outputs for regular testing, so just install + # an unsynced fakesink when someone asks for a 'testoutput'. + if self._config["audio"]["output"] == "testoutput": + self._outputs = Gst.ElementFactory.make("fakesink") + else: + self._outputs = _Outputs() + try: + self._outputs.add_output(self._config["audio"]["output"]) + except exceptions.AudioException: + process.exit_process() # TODO: move this up the chain + + self._handler.setup_event_handling(self._outputs.get_static_pad("sink")) + + def _setup_audio_sink(self): + audio_sink = Gst.ElementFactory.make("bin", "audio-sink") + queue = Gst.ElementFactory.make("queue") + volume = Gst.ElementFactory.make("volume") + + # Queue element to buy us time between the about-to-finish event and + # the actual switch, i.e. about to switch can block for longer thanks + # to this queue. + + # TODO: See if settings should be set to minimize latency. Previous + # setting breaks appsrc, and settings before that broke on a few + # systems. So leave the default to play it safe. + buffer_time = self._config["audio"]["buffer_time"] + if buffer_time is not None and buffer_time > 0: + queue.set_property("max-size-time", buffer_time * Gst.MSECOND) + + audio_sink.add(queue) + audio_sink.add(self._outputs) + audio_sink.add(volume) + + queue.link(volume) + volume.link(self._outputs) + + if self.mixer: + self.mixer.setup(volume, self.actor_ref.proxy().mixer) + + ghost_pad = Gst.GhostPad.new("sink", queue.get_static_pad("sink")) + audio_sink.add_pad(ghost_pad) + + self._playbin.set_property("audio-sink", audio_sink) + self._queue = queue + + def _teardown_mixer(self): + if self.mixer: + self.mixer.teardown() + + def _on_about_to_finish(self, element): + if self._thread == threading.current_thread(): + logger.error( + "about-to-finish in actor, aborting to avoid deadlock." + ) + return + + gst_logger.debug("Got about-to-finish event.") + if self._about_to_finish_callback: + logger.debug("Running about-to-finish callback.") + self._about_to_finish_callback() + + def _on_source_setup(self, element, source): + gst_logger.debug( + "Got source-setup signal: element=%s", source.__class__.__name__ + ) + + if source.get_factory().get_name() == "appsrc": + self._appsrc.configure(source) + else: + self._appsrc.reset() + + if self._live_stream and hasattr(source.props, "is_live"): + gst_logger.debug("Enabling live stream mode") + source.set_live(True) + + utils.setup_proxy(source, self._config["proxy"]) + + def set_uri(self, uri, live_stream=False): + """ + Set URI of audio to be played. + + You *MUST* call :meth:`prepare_change` before calling this method. + + :param uri: the URI to play + :type uri: string + :param live_stream: disables buffering, reducing latency for stream, + and discarding data when paused + :type live_stream: bool + """ + + # XXX: Hack to workaround issue on Mac OS X where volume level + # does not persist between track changes. mopidy/mopidy#886 + if self.mixer is not None: + current_volume = self.mixer.get_volume() + else: + current_volume = None + + self._pending_uri = uri + self._pending_tags = {} + self._live_stream = live_stream + self._playbin.set_property("uri", uri) + + if self.mixer is not None and current_volume is not None: + self.mixer.set_volume(current_volume) + + def set_appsrc( + self, caps, need_data=None, enough_data=None, seek_data=None + ): + """ + Switch to using appsrc for getting audio to be played. + + You *MUST* call :meth:`prepare_change` before calling this method. + + :param caps: GStreamer caps string describing the audio format to + expect + :type caps: string + :param need_data: callback for when appsrc needs data + :type need_data: callable which takes data length hint in ms + :param enough_data: callback for when appsrc has enough data + :type enough_data: callable + :param seek_data: callback for when data from a new position is needed + to continue playback + :type seek_data: callable which takes time position in ms + """ + self._appsrc.prepare( + Gst.Caps.from_string(caps), need_data, enough_data, seek_data + ) + uri = "appsrc://" + self._pending_uri = uri + self._playbin.set_property("uri", uri) + + def emit_data(self, buffer_): + """ + Call this to deliver raw audio data to be played. + + If the buffer is :class:`None`, the end-of-stream token is put on the + playbin. We will get a GStreamer message when the stream playback + reaches the token, and can then do any end-of-stream related tasks. + + Note that the URI must be set to ``appsrc://`` for this to work. + + Returns :class:`True` if data was delivered. + + :param buffer_: buffer to pass to appsrc + :type buffer_: :class:`Gst.Buffer` or :class:`None` + :rtype: boolean + """ + return self._appsrc.push(buffer_) + + def set_about_to_finish_callback(self, callback): + """ + Configure audio to use an about-to-finish callback. + + This should be used to achieve gapless playback. For this to work the + callback *MUST* call :meth:`set_uri` with the new URI to play and + block until this call has been made. :meth:`prepare_change` is not + needed before :meth:`set_uri` in this one special case. + + :param callable callback: Callback to run when we need the next URI. + """ + self._about_to_finish_callback = callback + + def get_position(self): + """ + Get position in milliseconds. + + :rtype: int + """ + success, position = self._playbin.query_position(Gst.Format.TIME) + + if not success: + # TODO: take state into account for this and possibly also return + # None as the unknown value instead of zero? + logger.debug("Position query failed") + return 0 + + return utils.clocktime_to_millisecond(position) + + def set_position(self, position): + """ + Set position in milliseconds. + + :param position: the position in milliseconds + :type position: int + :rtype: :class:`True` if successful, else :class:`False` + """ + # TODO: double check seek flags in use. + gst_position = utils.millisecond_to_clocktime(position) + gst_logger.debug("Sending flushing seek: position=%r", gst_position) + # Send seek event to the queue not the playbin. The default behavior + # for bins is to forward this event to all sinks. Which results in + # duplicate seek events making it to appsrc. Since elements are not + # allowed to act on the seek event, only modify it, this should be safe + # to do. + result = self._queue.seek_simple( + Gst.Format.TIME, Gst.SeekFlags.FLUSH, gst_position + ) + return result + + def start_playback(self): + """ + Notify GStreamer that it should start playback. + + :rtype: :class:`True` if successfull, else :class:`False` + """ + return self._set_state(Gst.State.PLAYING) + + def pause_playback(self): + """ + Notify GStreamer that it should pause playback. + + :rtype: :class:`True` if successfull, else :class:`False` + """ + return self._set_state(Gst.State.PAUSED) + + def prepare_change(self): + """ + Notify GStreamer that we are about to change state of playback. + + This function *MUST* be called before changing URIs or doing + changes like updating data that is being pushed. The reason for this + is that GStreamer will reset all its state when it changes to + :attr:`Gst.State.READY`. + """ + return self._set_state(Gst.State.READY) + + def stop_playback(self): + """ + Notify GStreamer that is should stop playback. + + :rtype: :class:`True` if successfull, else :class:`False` + """ + return self._set_state(Gst.State.NULL) + + def wait_for_state_change(self): + """Block until any pending state changes are complete. + + Should only be used by tests. + """ + self._playbin.get_state(timeout=Gst.CLOCK_TIME_NONE) + + def enable_sync_handler(self): + """Enable manual processing of messages from bus. + + Should only be used by tests. + """ + + def sync_handler(bus, message): + self._handler.on_message(bus, message) + return Gst.BusSyncReply.DROP + + bus = self._playbin.get_bus() + bus.set_sync_handler(sync_handler) + + def _set_state(self, state): + """ + Internal method for setting the raw GStreamer state. + + .. digraph:: gst_state_transitions + + graph [rankdir="LR"]; + node [fontsize=10]; + + "NULL" -> "READY" + "PAUSED" -> "PLAYING" + "PAUSED" -> "READY" + "PLAYING" -> "PAUSED" + "READY" -> "NULL" + "READY" -> "PAUSED" + + :param state: State to set playbin to. One of: `Gst.State.NULL`, + `Gst.State.READY`, `Gst.State.PAUSED` and `Gst.State.PLAYING`. + :type state: :class:`Gst.State` + :rtype: :class:`True` if successfull, else :class:`False` + """ + if state < Gst.State.PAUSED: + self._buffering = False + + self._target_state = state + result = self._playbin.set_state(state) + gst_logger.debug( + "Changing state to %s: result=%s", + state.value_name, + result.value_name, + ) + + if result == Gst.StateChangeReturn.FAILURE: + logger.warning( + "Setting GStreamer state to %s failed", state.value_name + ) + return False + # TODO: at this point we could already emit stopped event instead + # of faking it in the message handling when result=OK + return True + + # TODO: bake this into setup appsrc perhaps? + def set_metadata(self, track): + """ + Set track metadata for currently playing song. + + Only needs to be called by sources such as ``appsrc`` which do not + already inject tags in playbin, e.g. when using :meth:`emit_data` to + deliver raw audio data to GStreamer. + + :param track: the current track + :type track: :class:`mopidy.models.Track` + """ + taglist = Gst.TagList.new_empty() + artists = [a for a in (track.artists or []) if a.name] + + def set_value(tag, value): + gobject_value = GObject.Value() + gobject_value.init(GObject.TYPE_STRING) + gobject_value.set_string(value) + taglist.add_value(Gst.TagMergeMode.REPLACE, tag, gobject_value) + + # Default to blank data to trick shoutcast into clearing any previous + # values it might have. + # TODO: Verify if this works at all, likely it doesn't. + set_value(Gst.TAG_ARTIST, " ") + set_value(Gst.TAG_TITLE, " ") + set_value(Gst.TAG_ALBUM, " ") + + if artists: + set_value(Gst.TAG_ARTIST, ", ".join([a.name for a in artists])) + + if track.name: + set_value(Gst.TAG_TITLE, track.name) + + if track.album and track.album.name: + set_value(Gst.TAG_ALBUM, track.album.name) + + gst_logger.debug( + "Sending TAG event for track %r: %r", track.uri, taglist.to_string() + ) + event = Gst.Event.new_tag(taglist) + if self._pending_uri: + self._pending_metadata = event + else: + self._playbin.send_event(event) + + def get_current_tags(self): + """ + Get the currently playing media's tags. + + If no tags have been found, or nothing is playing this returns an empty + dictionary. For each set of tags we collect a tags_changed event is + emitted with the keys of the changes tags. After such calls users may + call this function to get the updated values. + + :rtype: {key: [values]} dict for the current media. + """ + # TODO: should this be a (deep) copy? most likely yes + # TODO: should we return None when stopped? + # TODO: support only fetching keys we care about? + return self._tags diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/constants.py b/venv/lib/python3.7/site-packages/mopidy/audio/constants.py new file mode 100644 index 0000000..c1046f4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/constants.py @@ -0,0 +1,14 @@ +class PlaybackState: + + """ + Enum of playback states. + """ + + #: Constant representing the paused state. + PAUSED = "paused" + + #: Constant representing the playing state. + PLAYING = "playing" + + #: Constant representing the stopped state. + STOPPED = "stopped" diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/listener.py b/venv/lib/python3.7/site-packages/mopidy/audio/listener.py new file mode 100644 index 0000000..1b49cf1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/listener.py @@ -0,0 +1,94 @@ +from mopidy import listener + + +class AudioListener(listener.Listener): + + """ + Marker interface for recipients of events sent by the audio actor. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in the core actor. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + """ + + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of audio listener events""" + listener.send(AudioListener, event, **kwargs) + + def reached_end_of_stream(self): + """ + Called whenever the end of the audio stream is reached. + + *MAY* be implemented by actor. + """ + pass + + def stream_changed(self, uri): + """ + Called whenever the audio stream changes. + + *MAY* be implemented by actor. + + :param string uri: URI the stream has started playing. + """ + pass + + def position_changed(self, position): + """ + Called whenever the position of the stream changes. + + *MAY* be implemented by actor. + + :param int position: Position in milliseconds. + """ + pass + + def state_changed(self, old_state, new_state, target_state): + """ + Called after the playback state have changed. + + Will be called for both immediate and async state changes in GStreamer. + + Target state is used to when we should be in the target state, but + temporarily need to switch to an other state. A typical example of this + is buffering. When this happens an event with + `old=PLAYING, new=PAUSED, target=PLAYING` will be emitted. Once we have + caught up a `old=PAUSED, new=PLAYING, target=None` event will be + be generated. + + Regular state changes will not have target state set as they are final + states which should be stable. + + *MAY* be implemented by actor. + + :param old_state: the state before the change + :type old_state: string from :class:`mopidy.core.PlaybackState` field + :param new_state: the state after the change + :type new_state: A :class:`mopidy.core.PlaybackState` field + :type new_state: string from :class:`mopidy.core.PlaybackState` field + :param target_state: the intended state + :type target_state: string from :class:`mopidy.core.PlaybackState` + field or :class:`None` if this is a final state. + """ + pass + + def tags_changed(self, tags): + """ + Called whenever the current audio stream's tags change. + + This event signals that some track metadata has been updated. This can + be metadata such as artists, titles, organization, or details about the + actual audio such as bit-rates, numbers of channels etc. + + For the available tag keys please refer to GStreamer documentation for + tags. + + *MAY* be implemented by actor. + + :param tags: The tags that have just been updated. + :type tags: :class:`set` of strings + """ + pass diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/scan.py b/venv/lib/python3.7/site-packages/mopidy/audio/scan.py new file mode 100644 index 0000000..beb29f0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/scan.py @@ -0,0 +1,302 @@ +import collections +import logging +import time + +from mopidy import exceptions +from mopidy.audio import tags as tags_lib +from mopidy.audio import utils +from mopidy.internal import log +from mopidy.internal.gi import Gst, GstPbutils + +# GST_ELEMENT_FACTORY_LIST: +_DECODER = 1 << 0 +_AUDIO = 1 << 50 +_DEMUXER = 1 << 5 +_DEPAYLOADER = 1 << 8 +_PARSER = 1 << 6 + +# GST_TYPE_AUTOPLUG_SELECT_RESULT: +_SELECT_TRY = 0 +_SELECT_EXPOSE = 1 + +_Result = collections.namedtuple( + "Result", ("uri", "tags", "duration", "seekable", "mime", "playable") +) + +logger = logging.getLogger(__name__) + + +def _trace(*args, **kwargs): + logger.log(log.TRACE_LOG_LEVEL, *args, **kwargs) + + +# TODO: replace with a scan(uri, timeout=1000, proxy_config=None)? +class Scanner: + + """ + Helper to get tags and other relevant info from URIs. + + :param timeout: timeout for scanning a URI in ms + :param proxy_config: dictionary containing proxy config strings. + :type event: int + """ + + def __init__(self, timeout=1000, proxy_config=None): + self._timeout_ms = int(timeout) + self._proxy_config = proxy_config or {} + + def scan(self, uri, timeout=None): + """ + Scan the given uri collecting relevant metadata. + + :param uri: URI of the resource to scan. + :type uri: string + :param timeout: timeout for scanning a URI in ms. Defaults to the + ``timeout`` value used when creating the scanner. + :type timeout: int + :return: A named tuple containing + ``(uri, tags, duration, seekable, mime)``. + ``tags`` is a dictionary of lists for all the tags we found. + ``duration`` is the length of the URI in milliseconds, or + :class:`None` if the URI has no duration. ``seekable`` is boolean. + indicating if a seek would succeed. + """ + timeout = int(timeout or self._timeout_ms) + tags, duration, seekable, mime = None, None, None, None + pipeline, signals = _setup_pipeline(uri, self._proxy_config) + + try: + _start_pipeline(pipeline) + tags, mime, have_audio, duration = _process(pipeline, timeout) + seekable = _query_seekable(pipeline) + finally: + signals.clear() + pipeline.set_state(Gst.State.NULL) + del pipeline + + return _Result(uri, tags, duration, seekable, mime, have_audio) + + +# Turns out it's _much_ faster to just create a new pipeline for every as +# decodebins and other elements don't seem to take well to being reused. +def _setup_pipeline(uri, proxy_config=None): + src = Gst.Element.make_from_uri(Gst.URIType.SRC, uri) + if not src: + raise exceptions.ScannerError(f"GStreamer can not open: {uri}") + + if proxy_config: + utils.setup_proxy(src, proxy_config) + + signals = utils.Signals() + pipeline = Gst.ElementFactory.make("pipeline") + pipeline.add(src) + + if _has_src_pads(src): + _setup_decodebin(src, src.get_static_pad("src"), pipeline, signals) + elif _has_dynamic_src_pad(src): + signals.connect(src, "pad-added", _setup_decodebin, pipeline, signals) + else: + raise exceptions.ScannerError("No pads found in source element.") + + return pipeline, signals + + +def _has_src_pads(element): + pads = [] + element.iterate_src_pads().foreach(pads.append) + return bool(pads) + + +def _has_dynamic_src_pad(element): + for template in element.get_pad_template_list(): + if template.direction == Gst.PadDirection.SRC: + if template.presence == Gst.PadPresence.SOMETIMES: + return True + return False + + +def _setup_decodebin(element, pad, pipeline, signals): + typefind = Gst.ElementFactory.make("typefind") + decodebin = Gst.ElementFactory.make("decodebin") + + for element in (typefind, decodebin): + pipeline.add(element) + element.sync_state_with_parent() + + pad.link(typefind.get_static_pad("sink")) + typefind.link(decodebin) + + signals.connect(typefind, "have-type", _have_type, decodebin) + signals.connect(decodebin, "pad-added", _pad_added, pipeline) + signals.connect(decodebin, "autoplug-select", _autoplug_select) + + +def _have_type(element, probability, caps, decodebin): + decodebin.set_property("sink-caps", caps) + struct = Gst.Structure.new_empty("have-type") + struct.set_value("caps", caps.get_structure(0)) + element.get_bus().post(Gst.Message.new_application(element, struct)) + + +def _pad_added(element, pad, pipeline): + sink = Gst.ElementFactory.make("fakesink") + sink.set_property("sync", False) + + pipeline.add(sink) + sink.sync_state_with_parent() + pad.link(sink.get_static_pad("sink")) + + if pad.query_caps().is_subset(Gst.Caps.from_string("audio/x-raw")): + # Probably won't happen due to autoplug-select fix, but lets play it + # safe until we've tested more. + struct = Gst.Structure.new_empty("have-audio") + element.get_bus().post(Gst.Message.new_application(element, struct)) + + +def _autoplug_select(element, pad, caps, factory): + if factory.list_is_type(_DECODER | _AUDIO): + struct = Gst.Structure.new_empty("have-audio") + element.get_bus().post(Gst.Message.new_application(element, struct)) + if not factory.list_is_type(_DEMUXER | _DEPAYLOADER | _PARSER): + return _SELECT_EXPOSE + return _SELECT_TRY + + +def _start_pipeline(pipeline): + result = pipeline.set_state(Gst.State.PAUSED) + if result == Gst.StateChangeReturn.NO_PREROLL: + pipeline.set_state(Gst.State.PLAYING) + + +def _query_duration(pipeline): + success, duration = pipeline.query_duration(Gst.Format.TIME) + if not success: + duration = None # Make sure error case preserves None. + elif duration < 0: + duration = None # Stream without duration. + else: + duration = int(duration // Gst.MSECOND) + return success, duration + + +def _query_seekable(pipeline): + query = Gst.Query.new_seeking(Gst.Format.TIME) + pipeline.query(query) + return query.parse_seeking()[1] + + +def _process(pipeline, timeout_ms): + bus = pipeline.get_bus() + tags = {} + mime = None + have_audio = False + missing_message = None + duration = None + + types = ( + Gst.MessageType.ELEMENT + | Gst.MessageType.APPLICATION + | Gst.MessageType.ERROR + | Gst.MessageType.EOS + | Gst.MessageType.ASYNC_DONE + | Gst.MessageType.DURATION_CHANGED + | Gst.MessageType.TAG + ) + + timeout = timeout_ms + start = int(time.time() * 1000) + while timeout > 0: + msg = bus.timed_pop_filtered(timeout * Gst.MSECOND, types) + if msg is None: + break + + if logger.isEnabledFor(log.TRACE_LOG_LEVEL) and msg.get_structure(): + debug_text = msg.get_structure().to_string() + if len(debug_text) > 77: + debug_text = debug_text[:77] + "..." + _trace("element %s: %s", msg.src.get_name(), debug_text) + + if msg.type == Gst.MessageType.ELEMENT: + if GstPbutils.is_missing_plugin_message(msg): + missing_message = msg + elif msg.type == Gst.MessageType.APPLICATION: + if msg.get_structure().get_name() == "have-type": + mime = msg.get_structure().get_value("caps").get_name() + if mime and ( + mime.startswith("text/") or mime == "application/xml" + ): + return tags, mime, have_audio, duration + elif msg.get_structure().get_name() == "have-audio": + have_audio = True + elif msg.type == Gst.MessageType.ERROR: + error, _debug = msg.parse_error() + if missing_message and not mime: + caps = missing_message.get_structure().get_value("detail") + mime = caps.get_structure(0).get_name() + return tags, mime, have_audio, duration + raise exceptions.ScannerError(str(error)) + elif msg.type == Gst.MessageType.EOS: + return tags, mime, have_audio, duration + elif msg.type == Gst.MessageType.ASYNC_DONE: + success, duration = _query_duration(pipeline) + if tags and success: + return tags, mime, have_audio, duration + + # Don't try workaround for non-seekable sources such as mmssrc: + if not _query_seekable(pipeline): + return tags, mime, have_audio, duration + + # Workaround for upstream bug which causes tags/duration to arrive + # after pre-roll. We get around this by starting to play the track + # and then waiting for a duration change. + # https://bugzilla.gnome.org/show_bug.cgi?id=763553 + logger.debug("Using workaround for duration missing before play.") + result = pipeline.set_state(Gst.State.PLAYING) + if result == Gst.StateChangeReturn.FAILURE: + return tags, mime, have_audio, duration + + elif msg.type == Gst.MessageType.DURATION_CHANGED and tags: + # VBR formats sometimes seem to not have a duration by the time we + # go back to paused. So just try to get it right away. + success, duration = _query_duration(pipeline) + pipeline.set_state(Gst.State.PAUSED) + if success: + return tags, mime, have_audio, duration + elif msg.type == Gst.MessageType.TAG: + taglist = msg.parse_tag() + # Note that this will only keep the last tag. + tags.update(tags_lib.convert_taglist(taglist)) + + timeout = timeout_ms - (int(time.time() * 1000) - start) + + raise exceptions.ScannerError(f"Timeout after {timeout_ms:d}ms") + + +if __name__ == "__main__": + import os + import sys + + from mopidy.internal import path + + logging.basicConfig( + format="%(asctime)-15s %(levelname)s %(message)s", + level=log.TRACE_LOG_LEVEL, + ) + + scanner = Scanner(5000) + for uri in sys.argv[1:]: + if not Gst.uri_is_valid(uri): + uri = path.path_to_uri(os.path.abspath(uri)) + try: + result = scanner.scan(uri) + for key in ("uri", "mime", "duration", "playable", "seekable"): + value = getattr(result, key) + print(f"{key:<20} {value}") + print("tags") + for tag, value in result.tags.items(): + line = f"{tag:<20} {value}" + if len(line) > 77: + line = line[:77] + "..." + print(line) + except exceptions.ScannerError as error: + print(f"{uri}: {error}") diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/tags.py b/venv/lib/python3.7/site-packages/mopidy/audio/tags.py new file mode 100644 index 0000000..f26c731 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/tags.py @@ -0,0 +1,161 @@ +import collections +import datetime +import logging +import numbers + +from mopidy.internal import log +from mopidy.internal.gi import GLib, Gst +from mopidy.models import Album, Artist, Track + +logger = logging.getLogger(__name__) + + +def convert_taglist(taglist): + """Convert a :class:`Gst.TagList` to plain Python types. + + Knows how to convert: + + - Dates + - Buffers + - Numbers + - Strings + - Booleans + + Unknown types will be ignored and trace logged. Tag keys are all strings + defined as part GStreamer under GstTagList_. + + .. _GstTagList: https://developer.gnome.org/gstreamer/stable/\ +gstreamer-GstTagList.html + + :param taglist: A GStreamer taglist to be converted. + :type taglist: :class:`Gst.TagList` + :rtype: dictionary of tag keys with a list of values. + """ + result = collections.defaultdict(list) + + for n in range(taglist.n_tags()): + tag = taglist.nth_tag_name(n) + + for i in range(taglist.get_tag_size(tag)): + value = taglist.get_value_index(tag, i) + + if isinstance(value, GLib.Date): + try: + date = datetime.date( + value.get_year(), value.get_month(), value.get_day() + ) + result[tag].append(date.isoformat()) + except ValueError: + logger.debug( + "Ignoring dodgy date value: %d-%d-%d", + value.get_year(), + value.get_month(), + value.get_day(), + ) + elif isinstance(value, Gst.DateTime): + result[tag].append(value.to_iso8601_string()) + elif isinstance(value, bytes): + result[tag].append(value.decode(errors="replace")) + elif isinstance(value, (str, bool, numbers.Number)): + result[tag].append(value) + elif isinstance(value, Gst.Sample): + data = _extract_sample_data(value) + if data: + result[tag].append(data) + else: + logger.log( + log.TRACE_LOG_LEVEL, + "Ignoring unknown tag data: %r = %r", + tag, + value, + ) + + # TODO: dict(result) to not leak the defaultdict, or just use setdefault? + return result + + +def _extract_sample_data(sample): + buf = sample.get_buffer() + if not buf: + return None + return buf.extract_dup(0, buf.get_size()) + + +# TODO: split based on "stream" and "track" based conversion? i.e. handle data +# from radios in it's own helper instead? +def convert_tags_to_track(tags): + """Convert our normalized tags to a track. + + :param tags: dictionary of tag keys with a list of values + :type tags: :class:`dict` + :rtype: :class:`mopidy.models.Track` + """ + album_kwargs = {} + track_kwargs = {} + + track_kwargs["composers"] = _artists(tags, Gst.TAG_COMPOSER) + track_kwargs["performers"] = _artists(tags, Gst.TAG_PERFORMER) + track_kwargs["artists"] = _artists( + tags, Gst.TAG_ARTIST, "musicbrainz-artistid", "musicbrainz-sortname" + ) + album_kwargs["artists"] = _artists( + tags, Gst.TAG_ALBUM_ARTIST, "musicbrainz-albumartistid" + ) + + track_kwargs["genre"] = "; ".join(tags.get(Gst.TAG_GENRE, [])) + track_kwargs["name"] = "; ".join(tags.get(Gst.TAG_TITLE, [])) + if not track_kwargs["name"]: + track_kwargs["name"] = "; ".join(tags.get(Gst.TAG_ORGANIZATION, [])) + + track_kwargs["comment"] = "; ".join(tags.get("comment", [])) + if not track_kwargs["comment"]: + track_kwargs["comment"] = "; ".join(tags.get(Gst.TAG_LOCATION, [])) + if not track_kwargs["comment"]: + track_kwargs["comment"] = "; ".join(tags.get(Gst.TAG_COPYRIGHT, [])) + + track_kwargs["track_no"] = tags.get(Gst.TAG_TRACK_NUMBER, [None])[0] + track_kwargs["disc_no"] = tags.get(Gst.TAG_ALBUM_VOLUME_NUMBER, [None])[0] + track_kwargs["bitrate"] = tags.get(Gst.TAG_BITRATE, [None])[0] + track_kwargs["musicbrainz_id"] = tags.get("musicbrainz-trackid", [None])[0] + + album_kwargs["name"] = tags.get(Gst.TAG_ALBUM, [None])[0] + album_kwargs["num_tracks"] = tags.get(Gst.TAG_TRACK_COUNT, [None])[0] + album_kwargs["num_discs"] = tags.get(Gst.TAG_ALBUM_VOLUME_COUNT, [None])[0] + album_kwargs["musicbrainz_id"] = tags.get("musicbrainz-albumid", [None])[0] + + album_kwargs["date"] = tags.get(Gst.TAG_DATE, [None])[0] + if not album_kwargs["date"]: + datetime = tags.get(Gst.TAG_DATE_TIME, [None])[0] + if datetime is not None: + album_kwargs["date"] = datetime.split("T")[0] + track_kwargs["date"] = album_kwargs["date"] + + # Clear out any empty values we found + track_kwargs = {k: v for k, v in track_kwargs.items() if v} + album_kwargs = {k: v for k, v in album_kwargs.items() if v} + + # Only bother with album if we have a name to show. + if album_kwargs.get("name"): + track_kwargs["album"] = Album(**album_kwargs) + + return Track(**track_kwargs) + + +def _artists(tags, artist_name, artist_id=None, artist_sortname=None): + # Name missing, don't set artist + if not tags.get(artist_name): + return None + + # One artist name and either id or sortname, include all available fields + if len(tags[artist_name]) == 1 and ( + artist_id in tags or artist_sortname in tags + ): + attrs = {"name": tags[artist_name][0]} + if artist_id in tags: + attrs["musicbrainz_id"] = tags[artist_id][0] + if artist_sortname in tags: + attrs["sortname"] = tags[artist_sortname][0] + return [Artist(**attrs)] + + # Multiple artist, provide artists with name only to avoid ambiguity. + return [Artist(name=name) for name in tags[artist_name]] diff --git a/venv/lib/python3.7/site-packages/mopidy/audio/utils.py b/venv/lib/python3.7/site-packages/mopidy/audio/utils.py new file mode 100644 index 0000000..7ec36d3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/audio/utils.py @@ -0,0 +1,100 @@ +from mopidy import httpclient +from mopidy.internal.gi import Gst + + +def calculate_duration(num_samples, sample_rate): + """Determine duration of samples using GStreamer helper for precise + math.""" + return Gst.util_uint64_scale(num_samples, Gst.SECOND, sample_rate) + + +def create_buffer(data, timestamp=None, duration=None): + """Create a new GStreamer buffer based on provided data. + + Mainly intended to keep gst imports out of non-audio modules. + + .. versionchanged:: 2.0 + ``capabilites`` argument was removed. + """ + if not data: + raise ValueError("Cannot create buffer without data") + buffer_ = Gst.Buffer.new_wrapped(data) + if timestamp is not None: + buffer_.pts = timestamp + if duration is not None: + buffer_.duration = duration + return buffer_ + + +def millisecond_to_clocktime(value): + """Convert a millisecond time to internal GStreamer time.""" + return value * Gst.MSECOND + + +def clocktime_to_millisecond(value): + """Convert an internal GStreamer time to millisecond time.""" + return value // Gst.MSECOND + + +def supported_uri_schemes(uri_schemes): + """Determine which URIs we can actually support from provided whitelist. + + :param uri_schemes: list/set of URIs to check support for. + :type uri_schemes: list or set or URI schemes as strings. + :rtype: set of URI schemes we can support via this GStreamer install. + """ + supported_schemes = set() + registry = Gst.Registry.get() + + for factory in registry.get_feature_list(Gst.ElementFactory): + for uri in factory.get_uri_protocols(): + if uri in uri_schemes: + supported_schemes.add(uri) + + return supported_schemes + + +def setup_proxy(element, config): + """Configure a GStreamer element with proxy settings. + + :param element: element to setup proxy in. + :type element: :class:`Gst.GstElement` + :param config: proxy settings to use. + :type config: :class:`dict` + """ + if not hasattr(element.props, "proxy") or not config.get("hostname"): + return + + element.set_property("proxy", httpclient.format_proxy(config, auth=False)) + element.set_property("proxy-id", config.get("username")) + element.set_property("proxy-pw", config.get("password")) + + +class Signals: + + """Helper for tracking gobject signal registrations""" + + def __init__(self): + self._ids = {} + + def connect(self, element, event, func, *args): + """Connect a function + args to signal event on an element. + + Each event may only be handled by one callback in this implementation. + """ + assert (element, event) not in self._ids + self._ids[(element, event)] = element.connect(event, func, *args) + + def disconnect(self, element, event): + """Disconnect whatever handler we have for an element+event pair. + + Does nothing it the handler has already been removed. + """ + signal_id = self._ids.pop((element, event), None) + if signal_id is not None: + element.disconnect(signal_id) + + def clear(self): + """Clear all registered signal handlers.""" + for element, event in list(self._ids): + element.disconnect(self._ids.pop((element, event))) diff --git a/venv/lib/python3.7/site-packages/mopidy/backend.py b/venv/lib/python3.7/site-packages/mopidy/backend.py new file mode 100644 index 0000000..40b4eca --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/backend.py @@ -0,0 +1,445 @@ +import logging + +import pykka + +from mopidy import listener + +logger = logging.getLogger(__name__) + + +class Backend: + + """Backend API + + If the backend has problems during initialization it should raise + :exc:`mopidy.exceptions.BackendError` with a descriptive error message. + This will make Mopidy print the error message and exit so that the user can + fix the issue. + + :param config: the entire Mopidy configuration + :type config: dict + :param audio: actor proxy for the audio subsystem + :type audio: :class:`pykka.ActorProxy` for :class:`mopidy.audio.Audio` + """ + + #: Actor proxy to an instance of :class:`mopidy.audio.Audio`. + #: + #: Should be passed to the backend constructor as the kwarg ``audio``, + #: which will then set this field. + audio = None + + #: The library provider. An instance of + #: :class:`~mopidy.backend.LibraryProvider`, or :class:`None` if + #: the backend doesn't provide a library. + library = None + + #: The playback provider. An instance of + #: :class:`~mopidy.backend.PlaybackProvider`, or :class:`None` if + #: the backend doesn't provide playback. + playback = None + + #: The playlists provider. An instance of + #: :class:`~mopidy.backend.PlaylistsProvider`, or class:`None` if + #: the backend doesn't provide playlists. + playlists = None + + #: List of URI schemes this backend can handle. + uri_schemes = [] + + # Because the providers is marked as pykka.traversable(), we can't get() + # them from another actor, and need helper methods to check if the + # providers are set or None. + + def has_library(self): + return self.library is not None + + def has_library_browse(self): + return self.has_library() and self.library.root_directory is not None + + def has_playback(self): + return self.playback is not None + + def has_playlists(self): + return self.playlists is not None + + def ping(self): + """Called to check if the actor is still alive.""" + return True + + +@pykka.traversable +class LibraryProvider: + + """ + :param backend: backend the controller is a part of + :type backend: :class:`mopidy.backend.Backend` + """ + + root_directory = None + """ + :class:`mopidy.models.Ref.directory` instance with a URI and name set + representing the root of this library's browse tree. URIs must + use one of the schemes supported by the backend, and name should + be set to a human friendly value. + + *MUST be set by any class that implements* :meth:`LibraryProvider.browse`. + """ + + def __init__(self, backend): + self.backend = backend + + def browse(self, uri): + """ + See :meth:`mopidy.core.LibraryController.browse`. + + If you implement this method, make sure to also set + :attr:`root_directory`. + + *MAY be implemented by subclass.* + """ + return [] + + def get_distinct(self, field, query=None): + """ + See :meth:`mopidy.core.LibraryController.get_distinct`. + + *MAY be implemented by subclass.* + + Default implementation will simply return an empty set. + + Note that backends should always return an empty set for unexpected + field types. + """ + return set() + + def get_images(self, uris): + """ + See :meth:`mopidy.core.LibraryController.get_images`. + + *MAY be implemented by subclass.* + + Default implementation will simply return an empty dictionary. + """ + return {} + + def lookup(self, uri): + """ + See :meth:`mopidy.core.LibraryController.lookup`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def refresh(self, uri=None): + """ + See :meth:`mopidy.core.LibraryController.refresh`. + + *MAY be implemented by subclass.* + """ + pass + + def search(self, query=None, uris=None, exact=False): + """ + See :meth:`mopidy.core.LibraryController.search`. + + *MAY be implemented by subclass.* + + .. versionadded:: 1.0 + The ``exact`` param which replaces the old ``find_exact``. + """ + pass + + +@pykka.traversable +class PlaybackProvider: + + """ + :param audio: the audio actor + :type audio: actor proxy to an instance of :class:`mopidy.audio.Audio` + :param backend: the backend + :type backend: :class:`mopidy.backend.Backend` + """ + + def __init__(self, audio, backend): + self.audio = audio + self.backend = backend + + def pause(self): + """ + Pause playback. + + *MAY be reimplemented by subclass.* + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.pause_playback().get() + + def play(self): + """ + Start playback. + + *MAY be reimplemented by subclass.* + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.start_playback().get() + + def prepare_change(self): + """ + Indicate that an URI change is about to happen. + + *MAY be reimplemented by subclass.* + + It is extremely unlikely it makes sense for any backends to override + this. For most practical purposes it should be considered an internal + call between backends and core that backend authors should not touch. + """ + self.audio.prepare_change().get() + + def translate_uri(self, uri): + """ + Convert custom URI scheme to real playable URI. + + *MAY be reimplemented by subclass.* + + This is very likely the *only* thing you need to override as a backend + author. Typically this is where you convert any Mopidy specific URI + to a real URI and then return it. If you can't convert the URI just + return :class:`None`. + + :param uri: the URI to translate + :type uri: string + :rtype: string or :class:`None` if the URI could not be translated + """ + return uri + + def is_live(self, uri): + """ + Decide if the URI should be threated as a live stream or not. + + *MAY be reimplemented by subclass.* + + Playing a source as a live stream disables buffering, which reduces + latency before playback starts, and discards data when paused. + + :param uri: the URI + :type uri: string + :rtype: bool + """ + return False + + def change_track(self, track): + """ + Swith to provided track. + + *MAY be reimplemented by subclass.* + + It is unlikely it makes sense for any backends to override + this. For most practical purposes it should be considered an internal + call between backends and core that backend authors should not touch. + + The default implementation will call :meth:`translate_uri` which + is what you want to implement. + + :param track: the track to play + :type track: :class:`mopidy.models.Track` + :rtype: :class:`True` if successful, else :class:`False` + """ + uri = self.translate_uri(track.uri) + if uri != track.uri: + logger.debug("Backend translated URI from %s to %s", track.uri, uri) + if not uri: + return False + self.audio.set_uri(uri, live_stream=self.is_live(uri)).get() + return True + + def resume(self): + """ + Resume playback at the same time position playback was paused. + + *MAY be reimplemented by subclass.* + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.start_playback().get() + + def seek(self, time_position): + """ + Seek to a given time position. + + *MAY be reimplemented by subclass.* + + :param time_position: time position in milliseconds + :type time_position: int + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.set_position(time_position).get() + + def stop(self): + """ + Stop playback. + + *MAY be reimplemented by subclass.* + + Should not be used for tracking if tracks have been played or when we + are done playing them. + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.stop_playback().get() + + def get_time_position(self): + """ + Get the current time position in milliseconds. + + *MAY be reimplemented by subclass.* + + :rtype: int + """ + return self.audio.get_position().get() + + +@pykka.traversable +class PlaylistsProvider: + + """ + A playlist provider exposes a collection of playlists, methods to + create/change/delete playlists in this collection, and lookup of any + playlist the backend knows about. + + :param backend: backend the controller is a part of + :type backend: :class:`mopidy.backend.Backend` instance + """ + + def __init__(self, backend): + self.backend = backend + + def as_list(self): + """ + Get a list of the currently available playlists. + + Returns a list of :class:`~mopidy.models.Ref` objects referring to the + playlists. In other words, no information about the playlists' content + is given. + + :rtype: list of :class:`mopidy.models.Ref` + + .. versionadded:: 1.0 + """ + raise NotImplementedError + + def get_items(self, uri): + """ + Get the items in a playlist specified by ``uri``. + + Returns a list of :class:`~mopidy.models.Ref` objects referring to the + playlist's items. + + If a playlist with the given ``uri`` doesn't exist, it returns + :class:`None`. + + :rtype: list of :class:`mopidy.models.Ref`, or :class:`None` + + .. versionadded:: 1.0 + """ + raise NotImplementedError + + def create(self, name): + """ + Create a new empty playlist with the given name. + + Returns a new playlist with the given name and an URI, or :class:`None` + on failure. + + *MUST be implemented by subclass.* + + :param name: name of the new playlist + :type name: string + :rtype: :class:`mopidy.models.Playlist` or :class:`None` + """ + raise NotImplementedError + + def delete(self, uri): + """ + Delete playlist identified by the URI. + + Returns :class:`True` if deleted, :class:`False` otherwise. + + *MUST be implemented by subclass.* + + :param uri: URI of the playlist to delete + :type uri: string + :rtype: :class:`bool` + + .. versionchanged:: 2.2 + Return type defined. + """ + raise NotImplementedError + + def lookup(self, uri): + """ + Lookup playlist with given URI in both the set of playlists and in any + other playlist source. + + Returns the playlists or :class:`None` if not found. + + *MUST be implemented by subclass.* + + :param uri: playlist URI + :type uri: string + :rtype: :class:`mopidy.models.Playlist` or :class:`None` + """ + raise NotImplementedError + + def refresh(self): + """ + Refresh the playlists in :attr:`playlists`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def save(self, playlist): + """ + Save the given playlist. + + The playlist must have an ``uri`` attribute set. To create a new + playlist with an URI, use :meth:`create`. + + Returns the saved playlist or :class:`None` on failure. + + *MUST be implemented by subclass.* + + :param playlist: the playlist to save + :type playlist: :class:`mopidy.models.Playlist` + :rtype: :class:`mopidy.models.Playlist` or :class:`None` + """ + raise NotImplementedError + + +class BackendListener(listener.Listener): + + """ + Marker interface for recipients of events sent by the backend actors. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in a backend actor. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + + Normally, only the Core actor should mix in this class. + """ + + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of backend listener events""" + listener.send(BackendListener, event, **kwargs) + + def playlists_loaded(self): + """ + Called when playlists are loaded or refreshed. + + *MAY* be implemented by actor. + """ + pass diff --git a/venv/lib/python3.7/site-packages/mopidy/commands.py b/venv/lib/python3.7/site-packages/mopidy/commands.py new file mode 100644 index 0000000..8eb345f --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/commands.py @@ -0,0 +1,489 @@ +import argparse +import collections +import contextlib +import logging +import os +import pathlib +import signal +import sys + +import pykka + +from mopidy import config as config_lib +from mopidy import exceptions +from mopidy.audio import Audio +from mopidy.core import Core +from mopidy.internal import deps, process, timer, versioning +from mopidy.internal.gi import GLib + +logger = logging.getLogger(__name__) + +_default_config = [ + (pathlib.Path(base) / "mopidy" / "mopidy.conf").resolve() + for base in GLib.get_system_config_dirs() + [GLib.get_user_config_dir()] +] +DEFAULT_CONFIG = ":".join(map(str, _default_config)) + + +def config_files_type(value): + return value.split(":") + + +def config_override_type(value): + try: + section, remainder = value.split("/", 1) + key, value = remainder.split("=", 1) + return (section.strip(), key.strip(), value.strip()) + except ValueError: + raise argparse.ArgumentTypeError( + f"{value} must have the format section/key=value" + ) + + +class _ParserError(Exception): + def __init__(self, message): + self.message = message + + +class _HelpError(Exception): + pass + + +class _ArgumentParser(argparse.ArgumentParser): + def error(self, message): + raise _ParserError(message) + + +class _HelpAction(argparse.Action): + def __init__(self, option_strings, dest=None, help=None): + super().__init__( + option_strings=option_strings, + dest=dest or argparse.SUPPRESS, + default=argparse.SUPPRESS, + nargs=0, + help=help, + ) + + def __call__(self, parser, namespace, values, option_string=None): + raise _HelpError() + + +class Command: + + """Command parser and runner for building trees of commands. + + This class provides a wraper around :class:`argparse.ArgumentParser` + for handling this type of command line application in a better way than + argprases own sub-parser handling. + """ + + help = None + #: Help text to display in help output. + + def __init__(self): + self._children = collections.OrderedDict() + self._arguments = [] + self._overrides = {} + + def _build(self): + actions = [] + parser = _ArgumentParser(add_help=False) + parser.register("action", "help", _HelpAction) + + for args, kwargs in self._arguments: + actions.append(parser.add_argument(*args, **kwargs)) + + parser.add_argument( + "_args", nargs=argparse.REMAINDER, help=argparse.SUPPRESS + ) + return parser, actions + + def add_child(self, name, command): + """Add a child parser to consider using. + + :param name: name to use for the sub-command that is being added. + :type name: string + """ + self._children[name] = command + + def add_argument(self, *args, **kwargs): + """Add an argument to the parser. + + This method takes all the same arguments as the + :class:`argparse.ArgumentParser` version of this method. + """ + self._arguments.append((args, kwargs)) + + def set(self, **kwargs): + """Override a value in the finaly result of parsing.""" + self._overrides.update(kwargs) + + def exit(self, status_code=0, message=None, usage=None): + """Optionally print a message and exit.""" + print("\n\n".join(m for m in (usage, message) if m)) + sys.exit(status_code) + + def format_usage(self, prog=None): + """Format usage for current parser.""" + actions = self._build()[1] + prog = prog or os.path.basename(sys.argv[0]) + return self._usage(actions, prog) + "\n" + + def _usage(self, actions, prog): + formatter = argparse.HelpFormatter(prog) + formatter.add_usage(None, actions, []) + return formatter.format_help().strip() + + def format_help(self, prog=None): + """Format help for current parser and children.""" + actions = self._build()[1] + prog = prog or os.path.basename(sys.argv[0]) + + formatter = argparse.HelpFormatter(prog) + formatter.add_usage(None, actions, []) + + if self.help: + formatter.add_text(self.help) + + if actions: + formatter.add_text("OPTIONS:") + formatter.start_section(None) + formatter.add_arguments(actions) + formatter.end_section() + + subhelp = [] + for name, child in self._children.items(): + child._subhelp(name, subhelp) + + if subhelp: + formatter.add_text("COMMANDS:") + subhelp.insert(0, "") + + return formatter.format_help() + "\n".join(subhelp) + + def _subhelp(self, name, result): + actions = self._build()[1] + + if self.help or actions: + formatter = argparse.HelpFormatter(name) + formatter.add_usage(None, actions, [], "") + formatter.start_section(None) + formatter.add_text(self.help) + formatter.start_section(None) + formatter.add_arguments(actions) + formatter.end_section() + formatter.end_section() + result.append(formatter.format_help()) + + for childname, child in self._children.items(): + child._subhelp(" ".join((name, childname)), result) + + def parse(self, args, prog=None): + """Parse command line arguments. + + Will recursively parse commands until a final parser is found or an + error occurs. In the case of errors we will print a message and exit. + Otherwise, any overrides are applied and the current parser stored + in the command attribute of the return value. + + :param args: list of arguments to parse + :type args: list of strings + :param prog: name to use for program + :type prog: string + :rtype: :class:`argparse.Namespace` + """ + prog = prog or os.path.basename(sys.argv[0]) + try: + return self._parse( + args, argparse.Namespace(), self._overrides.copy(), prog + ) + except _HelpError: + self.exit(0, self.format_help(prog)) + + def _parse(self, args, namespace, overrides, prog): + overrides.update(self._overrides) + parser, actions = self._build() + + try: + result = parser.parse_args(args, namespace) + except _ParserError as exc: + self.exit(1, str(exc), self._usage(actions, prog)) + + if not result._args: + for attr, value in overrides.items(): + setattr(result, attr, value) + delattr(result, "_args") + result.command = self + return result + + child = result._args.pop(0) + if child not in self._children: + usage = self._usage(actions, prog) + self.exit(1, f"unrecognized command: {child}", usage) + + return self._children[child]._parse( + result._args, result, overrides, " ".join([prog, child]) + ) + + def run(self, *args, **kwargs): + """Run the command. + + Must be implemented by sub-classes that are not simply an intermediate + in the command namespace. + """ + raise NotImplementedError + + +@contextlib.contextmanager +def _actor_error_handling(name): + try: + yield + except exceptions.BackendError as exc: + logger.error("Backend (%s) initialization error: %s", name, exc) + except exceptions.FrontendError as exc: + logger.error("Frontend (%s) initialization error: %s", name, exc) + except exceptions.MixerError as exc: + logger.error("Mixer (%s) initialization error: %s", name, exc) + except Exception: + logger.exception("Got un-handled exception from %s", name) + + +# TODO: move out of this utility class +class RootCommand(Command): + def __init__(self): + super().__init__() + self.set(base_verbosity_level=0) + self.add_argument( + "-h", "--help", action="help", help="Show this message and exit" + ) + self.add_argument( + "--version", + action="version", + version=f"Mopidy {versioning.get_version()}", + ) + self.add_argument( + "-q", + "--quiet", + action="store_const", + const=-1, + dest="verbosity_level", + help="less output (warning level)", + ) + self.add_argument( + "-v", + "--verbose", + action="count", + dest="verbosity_level", + default=0, + help="more output (repeat up to 4 times for even more)", + ) + self.add_argument( + "--config", + action="store", + dest="config_files", + type=config_files_type, + default=DEFAULT_CONFIG, + metavar="FILES", + help="config files to use, colon seperated, later files override", + ) + self.add_argument( + "-o", + "--option", + action="append", + dest="config_overrides", + type=config_override_type, + metavar="OPTIONS", + help="`section/key=value` values to override config options", + ) + + def run(self, args, config): + def on_sigterm(loop): + logger.info("GLib mainloop got SIGTERM. Exiting...") + loop.quit() + + loop = GLib.MainLoop() + GLib.unix_signal_add( + GLib.PRIORITY_DEFAULT, signal.SIGTERM, on_sigterm, loop + ) + + mixer_class = self.get_mixer_class(config, args.registry["mixer"]) + backend_classes = args.registry["backend"] + frontend_classes = args.registry["frontend"] + core = None + + exit_status_code = 0 + try: + mixer = None + if mixer_class is not None: + mixer = self.start_mixer(config, mixer_class) + if mixer: + self.configure_mixer(config, mixer) + audio = self.start_audio(config, mixer) + backends = self.start_backends(config, backend_classes, audio) + core = self.start_core(config, mixer, backends, audio) + self.start_frontends(config, frontend_classes, core) + logger.info("Starting GLib mainloop") + loop.run() + except ( + exceptions.BackendError, + exceptions.FrontendError, + exceptions.MixerError, + ): + logger.info("Initialization error. Exiting...") + exit_status_code = 1 + except KeyboardInterrupt: + logger.info("Interrupted. Exiting...") + except Exception: + logger.exception("Uncaught exception") + finally: + loop.quit() + self.stop_frontends(frontend_classes) + self.stop_core(core) + self.stop_backends(backend_classes) + self.stop_audio() + if mixer_class is not None: + self.stop_mixer(mixer_class) + process.stop_remaining_actors() + return exit_status_code + + def get_mixer_class(self, config, mixer_classes): + logger.debug( + "Available Mopidy mixers: %s", + ", ".join(m.__name__ for m in mixer_classes) or "none", + ) + + if config["audio"]["mixer"] == "none": + logger.debug("Mixer disabled") + return None + + selected_mixers = [ + m for m in mixer_classes if m.name == config["audio"]["mixer"] + ] + if len(selected_mixers) != 1: + logger.error( + 'Did not find unique mixer "%s". Alternatives are: %s', + config["audio"]["mixer"], + ", ".join([m.name for m in mixer_classes]) + ", none" or "none", + ) + process.exit_process() + return selected_mixers[0] + + def start_mixer(self, config, mixer_class): + logger.info("Starting Mopidy mixer: %s", mixer_class.__name__) + with _actor_error_handling(mixer_class.__name__): + mixer = mixer_class.start(config=config).proxy() + try: + mixer.ping().get() + return mixer + except pykka.ActorDeadError as exc: + logger.error("Actor died: %s", exc) + return None + + def configure_mixer(self, config, mixer): + volume = config["audio"]["mixer_volume"] + if volume is not None: + mixer.set_volume(volume) + logger.info("Mixer volume set to %d", volume) + else: + logger.debug("Mixer volume left unchanged") + + def start_audio(self, config, mixer): + logger.info("Starting Mopidy audio") + return Audio.start(config=config, mixer=mixer).proxy() + + def start_backends(self, config, backend_classes, audio): + logger.info( + "Starting Mopidy backends: %s", + ", ".join(b.__name__ for b in backend_classes) or "none", + ) + + backends = [] + for backend_class in backend_classes: + with _actor_error_handling(backend_class.__name__): + with timer.time_logger(backend_class.__name__): + backend = backend_class.start( + config=config, audio=audio + ).proxy() + backends.append(backend) + + # Block until all on_starts have finished, letting them run in parallel + for backend in backends[:]: + try: + backend.ping().get() + except pykka.ActorDeadError as exc: + backends.remove(backend) + logger.error("Actor died: %s", exc) + + return backends + + def start_core(self, config, mixer, backends, audio): + logger.info("Starting Mopidy core") + core = Core.start( + config=config, mixer=mixer, backends=backends, audio=audio + ).proxy() + core.setup().get() + return core + + def start_frontends(self, config, frontend_classes, core): + logger.info( + "Starting Mopidy frontends: %s", + ", ".join(f.__name__ for f in frontend_classes) or "none", + ) + + for frontend_class in frontend_classes: + with _actor_error_handling(frontend_class.__name__): + with timer.time_logger(frontend_class.__name__): + frontend_class.start(config=config, core=core) + + def stop_frontends(self, frontend_classes): + logger.info("Stopping Mopidy frontends") + for frontend_class in frontend_classes: + process.stop_actors_by_class(frontend_class) + + def stop_core(self, core): + logger.info("Stopping Mopidy core") + if core: + core.teardown().get() + process.stop_actors_by_class(Core) + + def stop_backends(self, backend_classes): + logger.info("Stopping Mopidy backends") + for backend_class in backend_classes: + process.stop_actors_by_class(backend_class) + + def stop_audio(self): + logger.info("Stopping Mopidy audio") + process.stop_actors_by_class(Audio) + + def stop_mixer(self, mixer_class): + logger.info("Stopping Mopidy mixer") + process.stop_actors_by_class(mixer_class) + + +class ConfigCommand(Command): + help = "Show currently active configuration." + + def __init__(self): + super().__init__() + self.set(base_verbosity_level=-1) + + def run(self, config, errors, schemas): + data = config_lib.format(config, schemas, errors) + + # Throw away all bytes that are not valid UTF-8 before printing + data = data.encode(errors="surrogateescape").decode(errors="replace") + + print(data) + return 0 + + +class DepsCommand(Command): + help = "Show dependencies and debug information." + + def __init__(self): + super().__init__() + self.set(base_verbosity_level=-1) + + def run(self): + print(deps.format_dependency_list()) + return 0 diff --git a/venv/lib/python3.7/site-packages/mopidy/config/__init__.py b/venv/lib/python3.7/site-packages/mopidy/config/__init__.py new file mode 100644 index 0000000..8db522c --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/config/__init__.py @@ -0,0 +1,321 @@ +import configparser +import itertools +import logging +import os +import pathlib +import re +from collections.abc import Mapping + +from mopidy.config import keyring +from mopidy.config.schemas import ConfigSchema, MapConfigSchema +from mopidy.config.types import ( + Boolean, + ConfigValue, + Deprecated, + DeprecatedValue, + Hostname, + Integer, + List, + LogColor, + LogLevel, + Path, + Port, + Secret, + String, +) +from mopidy.internal import path, versioning + +__all__ = [ + # TODO List everything that is reexported, not just the unused parts. + "ConfigValue", + "List", +] + +logger = logging.getLogger(__name__) + +_core_schema = ConfigSchema("core") +_core_schema["cache_dir"] = Path() +_core_schema["config_dir"] = Path() +_core_schema["data_dir"] = Path() +# MPD supports at most 10k tracks, some clients segfault when this is exceeded. +_core_schema["max_tracklist_length"] = Integer(minimum=1) +_core_schema["restore_state"] = Boolean(optional=True) + +_logging_schema = ConfigSchema("logging") +_logging_schema["verbosity"] = Integer(minimum=-1, maximum=4) +_logging_schema["format"] = String() +_logging_schema["color"] = Boolean() +_logging_schema["console_format"] = Deprecated() +_logging_schema["debug_format"] = Deprecated() +_logging_schema["debug_file"] = Deprecated() +_logging_schema["config_file"] = Path(optional=True) + +_loglevels_schema = MapConfigSchema("loglevels", LogLevel()) +_logcolors_schema = MapConfigSchema("logcolors", LogColor()) + +_audio_schema = ConfigSchema("audio") +_audio_schema["mixer"] = String() +_audio_schema["mixer_track"] = Deprecated() +_audio_schema["mixer_volume"] = Integer(optional=True, minimum=0, maximum=100) +_audio_schema["output"] = String() +_audio_schema["visualizer"] = Deprecated() +_audio_schema["buffer_time"] = Integer(optional=True, minimum=1) + +_proxy_schema = ConfigSchema("proxy") +_proxy_schema["scheme"] = String( + optional=True, choices=["http", "https", "socks4", "socks5"] +) +_proxy_schema["hostname"] = Hostname(optional=True) +_proxy_schema["port"] = Port(optional=True) +_proxy_schema["username"] = String(optional=True) +_proxy_schema["password"] = Secret(optional=True) + +# NOTE: if multiple outputs ever comes something like LogLevelConfigSchema +# _outputs_schema = config.AudioOutputConfigSchema() + +_schemas = [ + _core_schema, + _logging_schema, + _loglevels_schema, + _logcolors_schema, + _audio_schema, + _proxy_schema, +] + +_INITIAL_HELP = """ +# For further information about options in this file see: +# https://docs.mopidy.com/ +# +# The initial commented out values reflect the defaults as of: +# {versions} +# +# Available options and defaults might have changed since then, +# run `mopidy config` to see the current effective config and +# `mopidy --version` to check the current version. +""" + + +def read(config_file): + """Helper to load config defaults in same way across core and extensions""" + return pathlib.Path(config_file).read_text(errors="surrogateescape") + + +def load(files, ext_schemas, ext_defaults, overrides): + config_dir = pathlib.Path(__file__).parent + defaults = [read(config_dir / "default.conf")] + defaults.extend(ext_defaults) + raw_config = _load(files, defaults, keyring.fetch() + (overrides or [])) + + schemas = _schemas[:] + schemas.extend(ext_schemas) + return _validate(raw_config, schemas) + + +def format(config, ext_schemas, comments=None, display=True): + schemas = _schemas[:] + schemas.extend(ext_schemas) + return _format(config, comments or {}, schemas, display, False) + + +def format_initial(extensions_data): + config_dir = pathlib.Path(__file__).parent + defaults = [read(config_dir / "default.conf")] + defaults.extend(d.extension.get_default_config() for d in extensions_data) + raw_config = _load([], defaults, []) + + schemas = _schemas[:] + schemas.extend(d.extension.get_config_schema() for d in extensions_data) + + config, errors = _validate(raw_config, schemas) + + versions = [f"Mopidy {versioning.get_version()}"] + extensions_data = sorted( + extensions_data, key=lambda d: d.extension.dist_name + ) + for data in extensions_data: + versions.append(f"{data.extension.dist_name} {data.extension.version}") + + header = _INITIAL_HELP.strip().format(versions="\n# ".join(versions)) + formatted_config = _format( + config=config, comments={}, schemas=schemas, display=False, disable=True + ) + return header + "\n\n" + formatted_config + + +def _load(files, defaults, overrides): + parser = configparser.RawConfigParser() + + # TODO: simply return path to config file for defaults so we can load it + # all in the same way? + logger.info("Loading config from builtin defaults") + for default in defaults: + if isinstance(default, bytes): + default = default.decode() + parser.read_string(default) + + # Load config from a series of config files + for f in files: + f = path.expand_path(f) + if f.is_dir(): + for g in f.iterdir(): + if g.is_file() and g.suffix == ".conf": + _load_file(parser, g.resolve()) + else: + _load_file(parser, f.resolve()) + + raw_config = {} + for section in parser.sections(): + raw_config[section] = dict(parser.items(section)) + + logger.info("Loading config from command line options") + for section, key, value in overrides: + raw_config.setdefault(section, {})[key] = value + + return raw_config + + +def _load_file(parser, file_path): + if not file_path.exists(): + logger.debug( + f"Loading config from {file_path.as_uri()} failed; " + f"it does not exist" + ) + return + if not os.access(str(file_path), os.R_OK): + logger.warning( + f"Loading config from file_path.as_uri() failed; " + f"read permission missing" + ) + return + + try: + logger.info(f"Loading config from {file_path.as_uri()}") + with file_path.open("r") as fh: + parser.read_file(fh) + except configparser.MissingSectionHeaderError: + logger.warning( + f"Loading config from {file_path.as_uri()} failed; " + f"it does not have a config section" + ) + except configparser.ParsingError as e: + linenos = ", ".join(str(lineno) for lineno, line in e.errors) + logger.warning( + f"Config file {file_path.as_uri()} has errors; " + f"line {linenos} has been ignored" + ) + except OSError: + # TODO: if this is the initial load of logging config we might not + # have a logger at this point, we might want to handle this better. + logger.debug(f"Config file {file_path.as_uri()} not found; skipping") + + +def _validate(raw_config, schemas): + # Get validated config + config = {} + errors = {} + sections = set(raw_config) + for schema in schemas: + sections.discard(schema.name) + values = raw_config.get(schema.name, {}) + result, error = schema.deserialize(values) + if error: + errors[schema.name] = error + if result: + config[schema.name] = result + + for section in sections: + logger.debug(f"Ignoring unknown config section: {section}") + + return config, errors + + +def _format(config, comments, schemas, display, disable): + output = [] + for schema in schemas: + serialized = schema.serialize( + config.get(schema.name, {}), display=display + ) + if not serialized: + continue + output.append(f"[{schema.name}]") + for key, value in serialized.items(): + if isinstance(value, DeprecatedValue): + continue + comment = comments.get(schema.name, {}).get(key, "") + output.append(f"{key} =") + if value is not None: + output[-1] += " " + value + if comment: + output[-1] += " ; " + comment.capitalize() + if disable: + output[-1] = re.sub(r"^", "#", output[-1], flags=re.M) + output.append("") + return "\n".join(output).strip() + + +def _preprocess(config_string): + """Convert a raw config into a form that preserves comments etc.""" + results = ["[__COMMENTS__]"] + counter = itertools.count(0) + + section_re = re.compile(r"^(\[[^\]]+\])\s*(.+)$") + blank_line_re = re.compile(r"^\s*$") + comment_re = re.compile(r"^(#|;)") + inline_comment_re = re.compile(r" ;") + + def newlines(match): + return f"__BLANK{next(counter):d}__ =" + + def comments(match): + if match.group(1) == "#": + return f"__HASH{next(counter):d}__ =" + elif match.group(1) == ";": + return f"__SEMICOLON{next(counter):d}__ =" + + def inlinecomments(match): + return f"\n__INLINE{next(counter):d}__ =" + + def sections(match): + return ( + f"{match.group(1)}\n__SECTION{next(counter):d}__ = {match.group(2)}" + ) + + for line in config_string.splitlines(): + line = blank_line_re.sub(newlines, line) + line = section_re.sub(sections, line) + line = comment_re.sub(comments, line) + line = inline_comment_re.sub(inlinecomments, line) + results.append(line) + return "\n".join(results) + + +def _postprocess(config_string): + """Converts a preprocessed config back to original form.""" + flags = re.IGNORECASE | re.MULTILINE + result = re.sub(r"^\[__COMMENTS__\](\n|$)", "", config_string, flags=flags) + result = re.sub(r"\n__INLINE\d+__ =(.*)$", r" ;\g<1>", result, flags=flags) + result = re.sub(r"^__HASH\d+__ =(.*)$", r"#\g<1>", result, flags=flags) + result = re.sub(r"^__SEMICOLON\d+__ =(.*)$", r";\g<1>", result, flags=flags) + result = re.sub(r"\n__SECTION\d+__ =(.*)$", r"\g<1>", result, flags=flags) + result = re.sub(r"^__BLANK\d+__ =$", "", result, flags=flags) + return result + + +class Proxy(Mapping): + def __init__(self, data): + self._data = data + + def __getitem__(self, key): + item = self._data.__getitem__(key) + if isinstance(item, dict): + return Proxy(item) + return item + + def __iter__(self): + return self._data.__iter__() + + def __len__(self): + return self._data.__len__() + + def __repr__(self): + return f"Proxy({self._data!r})" diff --git a/venv/lib/python3.7/site-packages/mopidy/config/default.conf b/venv/lib/python3.7/site-packages/mopidy/config/default.conf new file mode 100644 index 0000000..643638f --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/config/default.conf @@ -0,0 +1,25 @@ +[core] +cache_dir = $XDG_CACHE_DIR/mopidy +config_dir = $XDG_CONFIG_DIR/mopidy +data_dir = $XDG_DATA_DIR/mopidy +max_tracklist_length = 10000 +restore_state = false + +[logging] +verbosity = 0 +format = %(levelname)-8s %(asctime)s [%(process)d:%(threadName)s] %(name)s\n %(message)s +color = true +config_file = + +[audio] +mixer = software +mixer_volume = +output = autoaudiosink +buffer_time = + +[proxy] +scheme = +hostname = +port = +username = +password = diff --git a/venv/lib/python3.7/site-packages/mopidy/config/keyring.py b/venv/lib/python3.7/site-packages/mopidy/config/keyring.py new file mode 100644 index 0000000..bb0a1a3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/config/keyring.py @@ -0,0 +1,177 @@ +import logging + +logger = logging.getLogger(__name__) + +try: + import dbus +except ImportError: + dbus = None + + +# XXX: Hack to workaround introspection bug caused by gnome-keyring, should be +# fixed by version 3.5 per: +# https://git.gnome.org/browse/gnome-keyring/commit/?id=5dccbe88eb94eea9934e2b7 +if dbus: + EMPTY_STRING = dbus.String("", variant_level=1) +else: + EMPTY_STRING = "" + + +FETCH_ERROR = ( + "Fetching passwords from your keyring failed. Any passwords " + "stored in the keyring will not be available." +) + + +def fetch(): + if not dbus: + logger.debug("%s (dbus not installed)", FETCH_ERROR) + return [] + + try: + bus = dbus.SessionBus() + except dbus.exceptions.DBusException as e: + logger.debug("%s (%s)", FETCH_ERROR, e) + return [] + + if not bus.name_has_owner("org.freedesktop.secrets"): + logger.debug( + "%s (org.freedesktop.secrets service not running)", FETCH_ERROR + ) + return [] + + service = _service(bus) + session = service.OpenSession("plain", EMPTY_STRING)[1] + items, locked = service.SearchItems({"service": "mopidy"}) + + if not locked and not items: + return [] + + if locked: + # There is a chance we can unlock without prompting the users... + items, prompt = service.Unlock(locked) + if prompt != "/": + _prompt(bus, prompt).Dismiss() + logger.debug("%s (Keyring is locked)", FETCH_ERROR) + return [] + + result = [] + secrets = service.GetSecrets(items, session, byte_arrays=True) + for item_path, values in secrets.items(): + session_path, parameters, value, content_type = values + attrs = _item_attributes(bus, item_path) + result.append((attrs["section"], attrs["key"], bytes(value))) + return result + + +def set(section, key, value): + """Store a secret config value for a given section/key. + + Indicates if storage failed or succeeded. + """ + if not dbus: + logger.debug( + "Saving %s/%s to keyring failed. (dbus not installed)", section, key + ) + return False + + try: + bus = dbus.SessionBus() + except dbus.exceptions.DBusException as e: + logger.debug("Saving %s/%s to keyring failed. (%s)", section, key, e) + return False + + if not bus.name_has_owner("org.freedesktop.secrets"): + logger.debug( + "Saving %s/%s to keyring failed. " + "(org.freedesktop.secrets service not running)", + section, + key, + ) + return False + + service = _service(bus) + collection = _collection(bus) + if not collection: + return False + + if isinstance(value, str): + value = value.encode() + + session = service.OpenSession("plain", EMPTY_STRING)[1] + secret = dbus.Struct( + (session, "", dbus.ByteArray(value), "plain/text; charset=utf8") + ) + label = f"mopidy: {section}/{key}" + attributes = {"service": "mopidy", "section": section, "key": key} + properties = { + "org.freedesktop.Secret.Item.Label": label, + "org.freedesktop.Secret.Item.Attributes": attributes, + } + + try: + item, prompt = collection.CreateItem(properties, secret, True) + except dbus.exceptions.DBusException as e: + # TODO: catch IsLocked errors etc. + logger.debug("Saving %s/%s to keyring failed. (%s)", section, key, e) + return False + + if prompt == "/": + return True + + _prompt(bus, prompt).Dismiss() + logger.debug( + "Saving secret %s/%s failed. (Keyring is locked)", section, key + ) + return False + + +def _service(bus): + return _interface( + bus, "/org/freedesktop/secrets", "org.freedesktop.Secret.Service" + ) + + +# NOTE: depending on versions and setup 'default' might not exists, so try and +# use it but fall back to the 'login' collection, and finally the 'session' one +# if all else fails. We should probably create a keyring/collection setting +# that allows users to set this so they have control over where their secrets +# get stored. +def _collection(bus): + for name in "aliases/default", "collection/login", "collection/session": + path = "/org/freedesktop/secrets/" + name + if _collection_exists(bus, path): + break + else: + return None + return _interface(bus, path, "org.freedesktop.Secret.Collection") + + +# NOTE: Hack to probe if a given collection actually exists. Needed to work +# around an introspection bug in setting passwords for non-existant aliases. +def _collection_exists(bus, path): + try: + item = _interface(bus, path, "org.freedesktop.DBus.Properties") + item.Get("org.freedesktop.Secret.Collection", "Label") + return True + except dbus.exceptions.DBusException: + return False + + +# NOTE: We could call prompt.Prompt('') to unlock the keyring when it is not +# '/', but we would then also have to arrange to setup signals to wait until +# this has been completed. So for now we just dismiss the prompt and expect +# keyrings to be unlocked. +def _prompt(bus, path): + return _interface(bus, path, "Prompt") + + +def _item_attributes(bus, path): + item = _interface(bus, path, "org.freedesktop.DBus.Properties") + result = item.Get("org.freedesktop.Secret.Item", "Attributes") + return {bytes(k): bytes(v) for k, v in result.items()} + + +def _interface(bus, path, interface): + obj = bus.get_object("org.freedesktop.secrets", path) + return dbus.Interface(obj, interface) diff --git a/venv/lib/python3.7/site-packages/mopidy/config/schemas.py b/venv/lib/python3.7/site-packages/mopidy/config/schemas.py new file mode 100644 index 0000000..df16588 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/config/schemas.py @@ -0,0 +1,125 @@ +import collections + +from mopidy.config import types + + +def _did_you_mean(name, choices): + """Suggest most likely setting based on levenshtein.""" + if not choices: + return None + + name = name.lower() + candidates = [(_levenshtein(name, c), c) for c in choices] + candidates.sort() + + if candidates[0][0] <= 3: + return candidates[0][1] + return None + + +def _levenshtein(a, b): + """Calculates the Levenshtein distance between a and b.""" + n, m = len(a), len(b) + if n > m: + return _levenshtein(b, a) + + current = range(n + 1) + for i in range(1, m + 1): + previous, current = current, [i] + [0] * n + for j in range(1, n + 1): + add, delete = previous[j] + 1, current[j - 1] + 1 + change = previous[j - 1] + if a[j - 1] != b[i - 1]: + change += 1 + current[j] = min(add, delete, change) + return current[n] + + +class ConfigSchema(collections.OrderedDict): + + """Logical group of config values that correspond to a config section. + + Schemas are set up by assigning config keys with config values to + instances. Once setup :meth:`deserialize` can be called with a dict of + values to process. For convienience we also support :meth:`format` method + that can used for converting the values to a dict that can be printed and + :meth:`serialize` for converting the values to a form suitable for + persistence. + """ + + def __init__(self, name): + super().__init__() + self.name = name + + def deserialize(self, values): + """Validates the given ``values`` using the config schema. + + Returns a tuple with cleaned values and errors. + """ + errors = {} + result = {} + + for key, value in values.items(): + try: + result[key] = self[key].deserialize(value) + except KeyError: # not in our schema + errors[key] = "unknown config key." + suggestion = _did_you_mean(key, self.keys()) + if suggestion: + errors[key] += f" Did you mean {suggestion!r}?" + except ValueError as e: # deserialization failed + result[key] = None + errors[key] = str(e) + + for key in self.keys(): + if isinstance(self[key], types.Deprecated): + result.pop(key, None) + elif key not in result and key not in errors: + result[key] = None + errors[key] = "config key not found." + + return result, errors + + def serialize(self, values, display=False): + """Converts the given ``values`` to a format suitable for persistence. + + If ``display`` is :class:`True` secret config values, like passwords, + will be masked out. + + Returns a dict of config keys and values.""" + result = collections.OrderedDict() + for key in self.keys(): + if key in values: + result[key] = self[key].serialize(values[key], display) + return result + + +class MapConfigSchema: + + """Schema for handling multiple unknown keys with the same type. + + Does not sub-class :class:`ConfigSchema`, but implements the same + serialize/deserialize interface. + """ + + def __init__(self, name, value_type): + self.name = name + self._value_type = value_type + + def deserialize(self, values): + errors = {} + result = {} + + for key, value in values.items(): + try: + result[key] = self._value_type.deserialize(value) + except ValueError as e: # deserialization failed + result[key] = None + errors[key] = str(e) + return result, errors + + def serialize(self, values, display=False): + result = collections.OrderedDict() + for key in sorted(values.keys()): + result[key] = self._value_type.serialize(values[key], display) + return result diff --git a/venv/lib/python3.7/site-packages/mopidy/config/types.py b/venv/lib/python3.7/site-packages/mopidy/config/types.py new file mode 100644 index 0000000..f9ffec5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/config/types.py @@ -0,0 +1,323 @@ +import logging +import re +import socket + +from mopidy.config import validators +from mopidy.internal import log, path + + +def decode(value): + if isinstance(value, bytes): + value = value.decode(errors="surrogateescape") + + for char in ("\\", "\n", "\t"): + value = value.replace( + char.encode(encoding="unicode-escape").decode(), char + ) + + return value + + +def encode(value): + if isinstance(value, bytes): + value = value.decode(errors="surrogateescape") + + for char in ("\\", "\n", "\t"): + value = value.replace( + char, char.encode(encoding="unicode-escape").decode() + ) + + return value + + +class DeprecatedValue: + pass + + +class ConfigValue: + """Represents a config key's value and how to handle it. + + Normally you will only be interacting with sub-classes for config values + that encode either deserialization behavior and/or validation. + + Each config value should be used for the following actions: + + 1. Deserializing from a raw string and validating, raising ValueError on + failure. + 2. Serializing a value back to a string that can be stored in a config. + 3. Formatting a value to a printable form (useful for masking secrets). + + :class:`None` values should not be deserialized, serialized or formatted, + the code interacting with the config should simply skip None config values. + """ + + def deserialize(self, value): + """Cast raw string to appropriate type.""" + return decode(value) + + def serialize(self, value, display=False): + """Convert value back to string for saving.""" + if value is None: + return "" + return str(value) + + +class Deprecated(ConfigValue): + """Deprecated value. + + Used for ignoring old config values that are no longer in use, but should + not cause the config parser to crash. + """ + + def deserialize(self, value): + return DeprecatedValue() + + def serialize(self, value, display=False): + return DeprecatedValue() + + +class String(ConfigValue): + """String value. + + Is decoded as utf-8 and \\n \\t escapes should work and be preserved. + """ + + def __init__(self, optional=False, choices=None): + self._required = not optional + self._choices = choices + + def deserialize(self, value): + value = decode(value).strip() + validators.validate_required(value, self._required) + if not value: + return None + validators.validate_choice(value, self._choices) + return value + + def serialize(self, value, display=False): + if value is None: + return "" + return encode(value) + + +class Secret(String): + """Secret string value. + + Is decoded as utf-8 and \\n \\t escapes should work and be preserved. + + Should be used for passwords, auth tokens etc. Will mask value when being + displayed. + """ + + def __init__(self, optional=False, choices=None): + self._required = not optional + self._choices = None # Choices doesn't make sense for secrets + + def serialize(self, value, display=False): + if value is not None and display: + return "********" + return super().serialize(value, display) + + +class Integer(ConfigValue): + """Integer value.""" + + def __init__( + self, minimum=None, maximum=None, choices=None, optional=False + ): + self._required = not optional + self._minimum = minimum + self._maximum = maximum + self._choices = choices + + def deserialize(self, value): + value = decode(value) + validators.validate_required(value, self._required) + if not value: + return None + value = int(value) + validators.validate_choice(value, self._choices) + validators.validate_minimum(value, self._minimum) + validators.validate_maximum(value, self._maximum) + return value + + +class Boolean(ConfigValue): + """Boolean value. + + Accepts ``1``, ``yes``, ``true``, and ``on`` with any casing as + :class:`True`. + + Accepts ``0``, ``no``, ``false``, and ``off`` with any casing as + :class:`False`. + """ + + true_values = ("1", "yes", "true", "on") + false_values = ("0", "no", "false", "off") + + def __init__(self, optional=False): + self._required = not optional + + def deserialize(self, value): + value = decode(value) + validators.validate_required(value, self._required) + if not value: + return None + if value.lower() in self.true_values: + return True + elif value.lower() in self.false_values: + return False + raise ValueError(f"invalid value for boolean: {value!r}") + + def serialize(self, value, display=False): + if value is True: + return "true" + elif value in (False, None): + return "false" + else: + raise ValueError(f"{value!r} is not a boolean") + + +class List(ConfigValue): + """List value. + + Supports elements split by commas or newlines. Newlines take presedence and + empty list items will be filtered out. + """ + + def __init__(self, optional=False): + self._required = not optional + + def deserialize(self, value): + value = decode(value) + if "\n" in value: + values = re.split(r"\s*\n\s*", value) + else: + values = re.split(r"\s*,\s*", value) + values = tuple(v.strip() for v in values if v.strip()) + validators.validate_required(values, self._required) + return tuple(values) + + def serialize(self, value, display=False): + if not value: + return "" + return "\n " + "\n ".join(encode(v) for v in value if v) + + +class LogColor(ConfigValue): + def deserialize(self, value): + value = decode(value) + validators.validate_choice(value.lower(), log.COLORS) + return value.lower() + + def serialize(self, value, display=False): + if value.lower() in log.COLORS: + return encode(value.lower()) + return "" + + +class LogLevel(ConfigValue): + """Log level value. + + Expects one of ``critical``, ``error``, ``warning``, ``info``, ``debug``, + or ``all``, with any casing. + """ + + levels = { + "critical": logging.CRITICAL, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, + "debug": logging.DEBUG, + "trace": log.TRACE_LOG_LEVEL, + "all": logging.NOTSET, + } + + def deserialize(self, value): + value = decode(value) + validators.validate_choice(value.lower(), self.levels.keys()) + return self.levels.get(value.lower()) + + def serialize(self, value, display=False): + lookup = {v: k for k, v in self.levels.items()} + if value in lookup: + return encode(lookup[value]) + return "" + + +class Hostname(ConfigValue): + """Network hostname value.""" + + def __init__(self, optional=False): + self._required = not optional + + def deserialize(self, value, display=False): + value = decode(value).strip() + validators.validate_required(value, self._required) + if not value: + return None + + socket_path = path.get_unix_socket_path(value) + if socket_path is not None: + path_str = Path(not self._required).deserialize(socket_path) + return f"unix:{path_str}" + + try: + socket.getaddrinfo(value, None) + except OSError: + raise ValueError("must be a resolveable hostname or valid IP") + + return value + + +class Port(Integer): + """Network port value. + + Expects integer in the range 0-65535, zero tells the kernel to simply + allocate a port for us. + """ + + def __init__(self, choices=None, optional=False): + super().__init__( + minimum=0, maximum=2 ** 16 - 1, choices=choices, optional=optional + ) + + +class _ExpandedPath(str): + def __new__(cls, original, expanded): + return super().__new__(cls, expanded) + + def __init__(self, original, expanded): + self.original = original + + +class Path(ConfigValue): + """File system path. + + The following expansions of the path will be done: + + - ``~`` to the current user's home directory + - ``$XDG_CACHE_DIR`` according to the XDG spec + - ``$XDG_CONFIG_DIR`` according to the XDG spec + - ``$XDG_DATA_DIR`` according to the XDG spec + - ``$XDG_MUSIC_DIR`` according to the XDG spec + """ + + def __init__(self, optional=False): + self._required = not optional + + def deserialize(self, value): + value = decode(value).strip() + expanded = path.expand_path(value) + validators.validate_required(value, self._required) + validators.validate_required(expanded, self._required) + if not value or expanded is None: + return None + return _ExpandedPath(value, expanded) + + def serialize(self, value, display=False): + if isinstance(value, _ExpandedPath): + value = value.original + if isinstance(value, bytes): + value = value.decode(errors="surrogateescape") + return value diff --git a/venv/lib/python3.7/site-packages/mopidy/config/validators.py b/venv/lib/python3.7/site-packages/mopidy/config/validators.py new file mode 100644 index 0000000..6f69cb5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/config/validators.py @@ -0,0 +1,39 @@ +# TODO: add validate regexp? + + +def validate_required(value, required): + """Validate that ``value`` is set if ``required`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize` on + the raw string, _not_ the converted value. + """ + if required and not value: + raise ValueError("must be set.") + + +def validate_choice(value, choices): + """Validate that ``value`` is one of the ``choices`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize`. + """ + if choices is not None and value not in choices: + names = ", ".join(repr(c) for c in choices) + raise ValueError(f"must be one of {names}, not {value}.") + + +def validate_minimum(value, minimum): + """Validate that ``value`` is at least ``minimum`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize`. + """ + if minimum is not None and value < minimum: + raise ValueError(f"{value!r} must be larger than {minimum!r}.") + + +def validate_maximum(value, maximum): + """Validate that ``value`` is at most ``maximum`` + + Normally called in :meth:`~mopidy.config.types.ConfigValue.deserialize`. + """ + if maximum is not None and value > maximum: + raise ValueError(f"{value!r} must be smaller than {maximum!r}.") diff --git a/venv/lib/python3.7/site-packages/mopidy/core/__init__.py b/venv/lib/python3.7/site-packages/mopidy/core/__init__.py new file mode 100644 index 0000000..4e73d17 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/__init__.py @@ -0,0 +1,9 @@ +# flake8: noqa +from .actor import Core +from .history import HistoryController +from .library import LibraryController +from .listener import CoreListener +from .mixer import MixerController +from .playback import PlaybackController, PlaybackState +from .playlists import PlaylistsController +from .tracklist import TracklistController diff --git a/venv/lib/python3.7/site-packages/mopidy/core/actor.py b/venv/lib/python3.7/site-packages/mopidy/core/actor.py new file mode 100644 index 0000000..a8b07fc --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/actor.py @@ -0,0 +1,276 @@ +import collections +import itertools +import logging + +import pykka + +import mopidy +from mopidy import audio, backend, mixer +from mopidy.audio import PlaybackState +from mopidy.core.history import HistoryController +from mopidy.core.library import LibraryController +from mopidy.core.listener import CoreListener +from mopidy.core.mixer import MixerController +from mopidy.core.playback import PlaybackController +from mopidy.core.playlists import PlaylistsController +from mopidy.core.tracklist import TracklistController +from mopidy.internal import path, storage, validation, versioning +from mopidy.internal.models import CoreState + +logger = logging.getLogger(__name__) + + +class Core( + pykka.ThreadingActor, + audio.AudioListener, + backend.BackendListener, + mixer.MixerListener, +): + + library = None + """An instance of :class:`~mopidy.core.LibraryController`""" + + history = None + """An instance of :class:`~mopidy.core.HistoryController`""" + + mixer = None + """An instance of :class:`~mopidy.core.MixerController`""" + + playback = None + """An instance of :class:`~mopidy.core.PlaybackController`""" + + playlists = None + """An instance of :class:`~mopidy.core.PlaylistsController`""" + + tracklist = None + """An instance of :class:`~mopidy.core.TracklistController`""" + + def __init__(self, config=None, mixer=None, backends=None, audio=None): + super().__init__() + + self._config = config + + self.backends = Backends(backends) + + self.library = pykka.traversable( + LibraryController(backends=self.backends, core=self) + ) + self.history = pykka.traversable(HistoryController()) + self.mixer = pykka.traversable(MixerController(mixer=mixer)) + self.playback = pykka.traversable( + PlaybackController(audio=audio, backends=self.backends, core=self) + ) + self.playlists = pykka.traversable( + PlaylistsController(backends=self.backends, core=self) + ) + self.tracklist = pykka.traversable(TracklistController(core=self)) + + self.audio = audio + + def get_uri_schemes(self): + """Get list of URI schemes we can handle""" + futures = [b.uri_schemes for b in self.backends] + results = pykka.get_all(futures) + uri_schemes = itertools.chain(*results) + return sorted(uri_schemes) + + def get_version(self): + """Get version of the Mopidy core API""" + return versioning.get_version() + + def reached_end_of_stream(self): + self.playback._on_end_of_stream() + + def stream_changed(self, uri): + self.playback._on_stream_changed(uri) + + def position_changed(self, position): + self.playback._on_position_changed(position) + + def state_changed(self, old_state, new_state, target_state): + # XXX: This is a temporary fix for issue #232 while we wait for a more + # permanent solution with the implementation of issue #234. When the + # Spotify play token is lost, the Spotify backend pauses audio + # playback, but mopidy.core doesn't know this, so we need to update + # mopidy.core's state to match the actual state in mopidy.audio. If we + # don't do this, clients will think that we're still playing. + + # We ignore cases when target state is set as this is buffering + # updates (at least for now) and we need to get #234 fixed... + if ( + new_state == PlaybackState.PAUSED + and not target_state + and self.playback.get_state() != PlaybackState.PAUSED + ): + self.playback.set_state(new_state) + self.playback._trigger_track_playback_paused() + + def playlists_loaded(self): + # Forward event from backend to frontends + CoreListener.send("playlists_loaded") + + def volume_changed(self, volume): + # Forward event from mixer to frontends + CoreListener.send("volume_changed", volume=volume) + + def mute_changed(self, mute): + # Forward event from mixer to frontends + CoreListener.send("mute_changed", mute=mute) + + def tags_changed(self, tags): + if not self.audio or "title" not in tags: + return + + tags = self.audio.get_current_tags().get() + if not tags: + return + + # TODO: this is a hack to make sure we don't emit stream title changes + # for plain tracks. We need a better way to decide if something is a + # stream. + if "title" in tags and tags["title"]: + title = tags["title"][0] + current_track = self.playback.get_current_track() + if current_track is not None and current_track.name != title: + self.playback._stream_title = title + CoreListener.send("stream_title_changed", title=title) + + def setup(self): + """Do not call this function. It is for internal use at startup.""" + try: + coverage = [] + if self._config and "restore_state" in self._config["core"]: + if self._config["core"]["restore_state"]: + coverage = [ + "tracklist", + "mode", + "play-last", + "mixer", + "history", + ] + if len(coverage): + self._load_state(coverage) + except Exception as e: + logger.warn("Restore state: Unexpected error: %s", str(e)) + + def teardown(self): + """Do not call this function. It is for internal use at shutdown.""" + try: + if self._config and "restore_state" in self._config["core"]: + if self._config["core"]["restore_state"]: + self._save_state() + except Exception as e: + logger.warn("Unexpected error while saving state: %s", str(e)) + + def _get_data_dir(self): + # get or create data director for core + data_dir_path = ( + path.expand_path(self._config["core"]["data_dir"]) / "core" + ) + path.get_or_create_dir(data_dir_path) + return data_dir_path + + def _get_state_file(self): + return self._get_data_dir() / "state.json.gz" + + def _save_state(self): + """ + Save current state to disk. + """ + + state_file = self._get_state_file() + logger.info("Saving state to %s", state_file) + + data = {} + data["version"] = mopidy.__version__ + data["state"] = CoreState( + tracklist=self.tracklist._save_state(), + history=self.history._save_state(), + playback=self.playback._save_state(), + mixer=self.mixer._save_state(), + ) + storage.dump(state_file, data) + logger.debug("Saving state done") + + def _load_state(self, coverage): + """ + Restore state from disk. + + Load state from disk and restore it. Parameter ``coverage`` + limits the amount of data to restore. Possible + values for ``coverage`` (list of one or more of): + + - 'tracklist' fill the tracklist + - 'mode' set tracklist properties (consume, random, repeat, single) + - 'play-last' restore play state ('tracklist' also required) + - 'mixer' set mixer volume and mute state + - 'history' restore history + + :param coverage: amount of data to restore + :type coverage: list of strings + """ + + state_file = self._get_state_file() + logger.info("Loading state from %s", state_file) + + data = storage.load(state_file) + + try: + # Try only once. If something goes wrong, the next start is clean. + state_file.unlink() + except OSError: + logger.info("Failed to delete %s", state_file) + + if "state" in data: + core_state = data["state"] + validation.check_instance(core_state, CoreState) + self.history._load_state(core_state.history, coverage) + self.tracklist._load_state(core_state.tracklist, coverage) + self.mixer._load_state(core_state.mixer, coverage) + # playback after tracklist + self.playback._load_state(core_state.playback, coverage) + logger.debug("Loading state done") + + +class Backends(list): + def __init__(self, backends): + super().__init__(backends) + + self.with_library = collections.OrderedDict() + self.with_library_browse = collections.OrderedDict() + self.with_playback = collections.OrderedDict() + self.with_playlists = collections.OrderedDict() + + backends_by_scheme = {} + + def name(b): + return b.actor_ref.actor_class.__name__ + + for b in backends: + try: + has_library = b.has_library().get() + has_library_browse = b.has_library_browse().get() + has_playback = b.has_playback().get() + has_playlists = b.has_playlists().get() + except Exception: + self.remove(b) + logger.exception( + "Fetching backend info for %s failed", + b.actor_ref.actor_class.__name__, + ) + + for scheme in b.uri_schemes.get(): + assert scheme not in backends_by_scheme, ( + f"Cannot add URI scheme {scheme!r} for {name(b)}, " + f"it is already handled by {name(backends_by_scheme[scheme])}" + ) + backends_by_scheme[scheme] = b + + if has_library: + self.with_library[scheme] = b + if has_library_browse: + self.with_library_browse[scheme] = b + if has_playback: + self.with_playback[scheme] = b + if has_playlists: + self.with_playlists[scheme] = b diff --git a/venv/lib/python3.7/site-packages/mopidy/core/history.py b/venv/lib/python3.7/site-packages/mopidy/core/history.py new file mode 100644 index 0000000..b7e9619 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/history.py @@ -0,0 +1,73 @@ +import copy +import logging +import time + +from mopidy import models +from mopidy.internal.models import HistoryState, HistoryTrack + +logger = logging.getLogger(__name__) + + +class HistoryController: + def __init__(self): + self._history = [] + + def _add_track(self, track): + """Add track to the playback history. + + Internal method for :class:`mopidy.core.PlaybackController`. + + :param track: track to add + :type track: :class:`mopidy.models.Track` + """ + if not isinstance(track, models.Track): + raise TypeError("Only Track objects can be added to the history") + + timestamp = int(time.time() * 1000) + + name_parts = [] + if track.artists: + name_parts.append( + ", ".join([artist.name for artist in track.artists]) + ) + if track.name is not None: + name_parts.append(track.name) + name = " - ".join(name_parts) + ref = models.Ref.track(uri=track.uri, name=name) + + self._history.insert(0, (timestamp, ref)) + + def get_length(self): + """Get the number of tracks in the history. + + :returns: the history length + :rtype: int + """ + return len(self._history) + + def get_history(self): + """Get the track history. + + The timestamps are milliseconds since epoch. + + :returns: the track history + :rtype: list of (timestamp, :class:`mopidy.models.Ref`) tuples + """ + return copy.copy(self._history) + + def _save_state(self): + # 500 tracks a 3 minutes -> 24 hours history + count_max = 500 + count = 1 + history_list = [] + for timestamp, track in self._history: + history_list.append(HistoryTrack(timestamp=timestamp, track=track)) + count += 1 + if count_max < count: + logger.info("Limiting history to %s tracks", count_max) + break + return HistoryState(history=history_list) + + def _load_state(self, state, coverage): + if state and "history" in coverage: + self._history = [(h.timestamp, h.track) for h in state.history] diff --git a/venv/lib/python3.7/site-packages/mopidy/core/library.py b/venv/lib/python3.7/site-packages/mopidy/core/library.py new file mode 100644 index 0000000..b3d7630 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/library.py @@ -0,0 +1,351 @@ +import collections +import contextlib +import logging +import operator +import urllib +from collections.abc import Mapping + +from mopidy import exceptions, models +from mopidy.internal import validation + +logger = logging.getLogger(__name__) + + +@contextlib.contextmanager +def _backend_error_handling(backend, reraise=None): + try: + yield + except exceptions.ValidationError as e: + logger.error( + "%s backend returned bad data: %s", + backend.actor_ref.actor_class.__name__, + e, + ) + except Exception as e: + if reraise and isinstance(e, reraise): + raise + logger.exception( + "%s backend caused an exception.", + backend.actor_ref.actor_class.__name__, + ) + + +class LibraryController: + def __init__(self, backends, core): + self.backends = backends + self.core = core + + def _get_backend(self, uri): + uri_scheme = urllib.parse.urlparse(uri).scheme + return self.backends.with_library.get(uri_scheme, None) + + def _get_backends_to_uris(self, uris): + if uris: + backends_to_uris = collections.defaultdict(list) + for uri in uris: + backend = self._get_backend(uri) + if backend is not None: + backends_to_uris[backend].append(uri) + else: + backends_to_uris = { + b: None for b in self.backends.with_library.values() + } + return backends_to_uris + + def browse(self, uri): + """ + Browse directories and tracks at the given ``uri``. + + ``uri`` is a string which represents some directory belonging to a + backend. To get the intial root directories for backends pass + :class:`None` as the URI. + + Returns a list of :class:`mopidy.models.Ref` objects for the + directories and tracks at the given ``uri``. + + The :class:`~mopidy.models.Ref` objects representing tracks keep the + track's original URI. A matching pair of objects can look like this:: + + Track(uri='dummy:/foo.mp3', name='foo', artists=..., album=...) + Ref.track(uri='dummy:/foo.mp3', name='foo') + + The :class:`~mopidy.models.Ref` objects representing directories have + backend specific URIs. These are opaque values, so no one but the + backend that created them should try and derive any meaning from them. + The only valid exception to this is checking the scheme, as it is used + to route browse requests to the correct backend. + + For example, the dummy library's ``/bar`` directory could be returned + like this:: + + Ref.directory(uri='dummy:directory:/bar', name='bar') + + :param string uri: URI to browse + :rtype: list of :class:`mopidy.models.Ref` + + .. versionadded:: 0.18 + """ + if uri is None: + return self._roots() + elif not uri.strip(): + return [] + validation.check_uri(uri) + return self._browse(uri) + + def _roots(self): + directories = set() + backends = self.backends.with_library_browse.values() + futures = {b: b.library.root_directory for b in backends} + for backend, future in futures.items(): + with _backend_error_handling(backend): + root = future.get() + validation.check_instance(root, models.Ref) + directories.add(root) + return sorted(directories, key=operator.attrgetter("name")) + + def _browse(self, uri): + scheme = urllib.parse.urlparse(uri).scheme + backend = self.backends.with_library_browse.get(scheme) + + if not backend: + return [] + + with _backend_error_handling(backend): + result = backend.library.browse(uri).get() + validation.check_instances(result, models.Ref) + return result + + return [] + + def get_distinct(self, field, query=None): + """ + List distinct values for a given field from the library. + + This has mainly been added to support the list commands the MPD + protocol supports in a more sane fashion. Other frontends are not + recommended to use this method. + + :param string field: One of ``track``, ``artist``, ``albumartist``, + ``album``, ``composer``, ``performer``, ``date`` or ``genre``. + :param dict query: Query to use for limiting results, see + :meth:`search` for details about the query format. + :rtype: set of values corresponding to the requested field type. + + .. versionadded:: 1.0 + """ + validation.check_choice(field, validation.DISTINCT_FIELDS) + query is None or validation.check_query(query) # TODO: normalize? + + result = set() + futures = { + b: b.library.get_distinct(field, query) + for b in self.backends.with_library.values() + } + for backend, future in futures.items(): + with _backend_error_handling(backend): + values = future.get() + if values is not None: + validation.check_instances(values, str) + result.update(values) + return result + + def get_images(self, uris): + """Lookup the images for the given URIs + + Backends can use this to return image URIs for any URI they know about + be it tracks, albums, playlists. The lookup result is a dictionary + mapping the provided URIs to lists of images. + + Unknown URIs or URIs the corresponding backend couldn't find anything + for will simply return an empty list for that URI. + + :param uris: list of URIs to find images for + :type uris: list of string + :rtype: {uri: tuple of :class:`mopidy.models.Image`} + + .. versionadded:: 1.0 + """ + validation.check_uris(uris) + + futures = { + backend: backend.library.get_images(backend_uris) + for (backend, backend_uris) in self._get_backends_to_uris( + uris + ).items() + if backend_uris + } + + results = {uri: tuple() for uri in uris} + for backend, future in futures.items(): + with _backend_error_handling(backend): + if future.get() is None: + continue + validation.check_instance(future.get(), Mapping) + for uri, images in future.get().items(): + if uri not in uris: + raise exceptions.ValidationError( + f"Got unknown image URI: {uri}" + ) + validation.check_instances(images, models.Image) + results[uri] += tuple(images) + return results + + def lookup(self, uris): + """ + Lookup the given URIs. + + If the URI expands to multiple tracks, the returned list will contain + them all. + + :param uris: track URIs + :type uris: list of string + :rtype: {uri: list of :class:`mopidy.models.Track`} + """ + validation.check_uris(uris) + + futures = {} + results = {u: [] for u in uris} + + # TODO: lookup(uris) to backend APIs + for backend, backend_uris in self._get_backends_to_uris(uris).items(): + if backend_uris: + for u in backend_uris: + futures[(backend, u)] = backend.library.lookup(u) + + for (backend, u), future in futures.items(): + with _backend_error_handling(backend): + result = future.get() + if result is not None: + validation.check_instances(result, models.Track) + # TODO Consider making Track.uri field mandatory, and + # then remove this filtering of tracks without URIs. + results[u] = [r for r in result if r.uri] + + return results + + def refresh(self, uri=None): + """ + Refresh library. Limit to URI and below if an URI is given. + + :param uri: directory or track URI + :type uri: string + """ + uri is None or validation.check_uri(uri) + + futures = {} + backends = {} + uri_scheme = urllib.parse.urlparse(uri).scheme if uri else None + + for backend_scheme, backend in self.backends.with_library.items(): + backends.setdefault(backend, set()).add(backend_scheme) + + for backend, backend_schemes in backends.items(): + if uri_scheme is None or uri_scheme in backend_schemes: + futures[backend] = backend.library.refresh(uri) + + for backend, future in futures.items(): + with _backend_error_handling(backend): + future.get() + + def search(self, query, uris=None, exact=False): + """ + Search the library for tracks where ``field`` contains ``values``. + + ``field`` can be one of ``uri``, ``track_name``, ``album``, ``artist``, + ``albumartist``, ``composer``, ``performer``, ``track_no``, ``genre``, + ``date``, ``comment``, or ``any``. + + If ``uris`` is given, the search is limited to results from within the + URI roots. For example passing ``uris=['file:']`` will limit the search + to the local backend. + + Examples:: + + # Returns results matching 'a' in any backend + search({'any': ['a']}) + + # Returns results matching artist 'xyz' in any backend + search({'artist': ['xyz']}) + + # Returns results matching 'a' and 'b' and artist 'xyz' in any + # backend + search({'any': ['a', 'b'], 'artist': ['xyz']}) + + # Returns results matching 'a' if within the given URI roots + # "file:///media/music" and "spotify:" + search({'any': ['a']}, uris=['file:///media/music', 'spotify:']) + + # Returns results matching artist 'xyz' and 'abc' in any backend + search({'artist': ['xyz', 'abc']}) + + :param query: one or more queries to search for + :type query: dict + :param uris: zero or more URI roots to limit the search to + :type uris: list of string or :class:`None` + :param exact: if the search should use exact matching + :type exact: :class:`bool` + :rtype: list of :class:`mopidy.models.SearchResult` + + .. versionadded:: 1.0 + The ``exact`` keyword argument. + """ + query = _normalize_query(query) + + uris is None or validation.check_uris(uris) + validation.check_query(query) + validation.check_boolean(exact) + + if not query: + return [] + + futures = {} + for backend, backend_uris in self._get_backends_to_uris(uris).items(): + futures[backend] = backend.library.search( + query=query, uris=backend_uris, exact=exact + ) + + # Some of our tests check for LookupError to catch bad queries. This is + # silly and should be replaced with query validation before passing it + # to the backends. + reraise = (TypeError, LookupError) + + results = [] + for backend, future in futures.items(): + try: + with _backend_error_handling(backend, reraise=reraise): + result = future.get() + if result is not None: + validation.check_instance(result, models.SearchResult) + results.append(result) + except TypeError: + backend_name = backend.actor_ref.actor_class.__name__ + logger.warning( + '%s does not implement library.search() with "exact" ' + "support. Please upgrade it.", + backend_name, + ) + + return results + + +def _normalize_query(query): + broken_client = False + # TODO: this breaks if query is not a dictionary like object... + for (field, values) in query.items(): + if isinstance(values, str): + broken_client = True + query[field] = [values] + if broken_client: + logger.warning( + "A client or frontend made a broken library search. Values in " + "queries must be lists of strings, not a string. Please check what" + " sent this query and file a bug. Query: %s", + query, + ) + if not query: + logger.warning( + "A client or frontend made a library search with an empty query. " + "This is strongly discouraged. Please check what sent this query " + "and file a bug." + ) + return query diff --git a/venv/lib/python3.7/site-packages/mopidy/core/listener.py b/venv/lib/python3.7/site-packages/mopidy/core/listener.py new file mode 100644 index 0000000..b022225 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/listener.py @@ -0,0 +1,187 @@ +from mopidy import listener + + +class CoreListener(listener.Listener): + + """ + Marker interface for recipients of events sent by the core actor. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in the core actor. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + """ + + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of core listener events""" + listener.send(CoreListener, event, **kwargs) + + def on_event(self, event, **kwargs): + """ + Called on all events. + + *MAY* be implemented by actor. By default, this method forwards the + event to the specific event methods. + + :param event: the event name + :type event: string + :param kwargs: any other arguments to the specific event handlers + """ + # Just delegate to parent, entry mostly for docs. + super().on_event(event, **kwargs) + + def track_playback_paused(self, tl_track, time_position): + """ + Called whenever track playback is paused. + + *MAY* be implemented by actor. + + :param tl_track: the track that was playing when playback paused + :type tl_track: :class:`mopidy.models.TlTrack` + :param time_position: the time position in milliseconds + :type time_position: int + """ + pass + + def track_playback_resumed(self, tl_track, time_position): + """ + Called whenever track playback is resumed. + + *MAY* be implemented by actor. + + :param tl_track: the track that was playing when playback resumed + :type tl_track: :class:`mopidy.models.TlTrack` + :param time_position: the time position in milliseconds + :type time_position: int + """ + pass + + def track_playback_started(self, tl_track): + """ + Called whenever a new track starts playing. + + *MAY* be implemented by actor. + + :param tl_track: the track that just started playing + :type tl_track: :class:`mopidy.models.TlTrack` + """ + pass + + def track_playback_ended(self, tl_track, time_position): + """ + Called whenever playback of a track ends. + + *MAY* be implemented by actor. + + :param tl_track: the track that was played before playback stopped + :type tl_track: :class:`mopidy.models.TlTrack` + :param time_position: the time position in milliseconds + :type time_position: int + """ + pass + + def playback_state_changed(self, old_state, new_state): + """ + Called whenever playback state is changed. + + *MAY* be implemented by actor. + + :param old_state: the state before the change + :type old_state: string from :class:`mopidy.core.PlaybackState` field + :param new_state: the state after the change + :type new_state: string from :class:`mopidy.core.PlaybackState` field + """ + pass + + def tracklist_changed(self): + """ + Called whenever the tracklist is changed. + + *MAY* be implemented by actor. + """ + pass + + def playlists_loaded(self): + """ + Called when playlists are loaded or refreshed. + + *MAY* be implemented by actor. + """ + pass + + def playlist_changed(self, playlist): + """ + Called whenever a playlist is changed. + + *MAY* be implemented by actor. + + :param playlist: the changed playlist + :type playlist: :class:`mopidy.models.Playlist` + """ + pass + + def playlist_deleted(self, uri): + """ + Called whenever a playlist is deleted. + + *MAY* be implemented by actor. + + :param uri: the URI of the deleted playlist + :type uri: string + """ + pass + + def options_changed(self): + """ + Called whenever an option is changed. + + *MAY* be implemented by actor. + """ + pass + + def volume_changed(self, volume): + """ + Called whenever the volume is changed. + + *MAY* be implemented by actor. + + :param volume: the new volume in the range [0..100] + :type volume: int + """ + pass + + def mute_changed(self, mute): + """ + Called whenever the mute state is changed. + + *MAY* be implemented by actor. + + :param mute: the new mute state + :type mute: boolean + """ + pass + + def seeked(self, time_position): + """ + Called whenever the time position changes by an unexpected amount, e.g. + at seek to a new time position. + + *MAY* be implemented by actor. + + :param time_position: the position that was seeked to in milliseconds + :type time_position: int + """ + pass + + def stream_title_changed(self, title): + """ + Called whenever the currently playing stream title changes. + + *MAY* be implemented by actor. + + :param title: the new stream title + :type title: string + """ + pass diff --git a/venv/lib/python3.7/site-packages/mopidy/core/mixer.py b/venv/lib/python3.7/site-packages/mopidy/core/mixer.py new file mode 100644 index 0000000..5c81cd4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/mixer.py @@ -0,0 +1,111 @@ +import contextlib +import logging + +from mopidy import exceptions +from mopidy.internal import validation +from mopidy.internal.models import MixerState + +logger = logging.getLogger(__name__) + + +@contextlib.contextmanager +def _mixer_error_handling(mixer): + try: + yield + except exceptions.ValidationError as e: + logger.error( + "%s mixer returned bad data: %s", + mixer.actor_ref.actor_class.__name__, + e, + ) + except Exception: + logger.exception( + "%s mixer caused an exception.", + mixer.actor_ref.actor_class.__name__, + ) + + +class MixerController: + def __init__(self, mixer): + self._mixer = mixer + + def get_volume(self): + """Get the volume. + + Integer in range [0..100] or :class:`None` if unknown. + + The volume scale is linear. + """ + if self._mixer is None: + return None + + with _mixer_error_handling(self._mixer): + volume = self._mixer.get_volume().get() + volume is None or validation.check_integer(volume, min=0, max=100) + return volume + + return None + + def set_volume(self, volume): + """Set the volume. + + The volume is defined as an integer in range [0..100]. + + The volume scale is linear. + + Returns :class:`True` if call is successful, otherwise :class:`False`. + """ + validation.check_integer(volume, min=0, max=100) + + if self._mixer is None: + return False # TODO: 2.0 return None + + with _mixer_error_handling(self._mixer): + result = self._mixer.set_volume(volume).get() + validation.check_instance(result, bool) + return result + + return False + + def get_mute(self): + """Get mute state. + + :class:`True` if muted, :class:`False` unmuted, :class:`None` if + unknown. + """ + if self._mixer is None: + return None + + with _mixer_error_handling(self._mixer): + mute = self._mixer.get_mute().get() + mute is None or validation.check_instance(mute, bool) + return mute + + return None + + def set_mute(self, mute): + """Set mute state. + + :class:`True` to mute, :class:`False` to unmute. + + Returns :class:`True` if call is successful, otherwise :class:`False`. + """ + validation.check_boolean(mute) + if self._mixer is None: + return False # TODO: 2.0 return None + + with _mixer_error_handling(self._mixer): + result = self._mixer.set_mute(bool(mute)).get() + validation.check_instance(result, bool) + return result + + return False + + def _save_state(self): + return MixerState(volume=self.get_volume(), mute=self.get_mute()) + + def _load_state(self, state, coverage): + if state and "mixer" in coverage: + self.set_mute(state.mute) + if state.volume: + self.set_volume(state.volume) diff --git a/venv/lib/python3.7/site-packages/mopidy/core/playback.py b/venv/lib/python3.7/site-packages/mopidy/core/playback.py new file mode 100644 index 0000000..15611d4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/playback.py @@ -0,0 +1,558 @@ +import logging +import urllib + +from pykka.messages import ProxyCall + +from mopidy.audio import PlaybackState +from mopidy.core import listener +from mopidy.internal import deprecation, models, validation + +logger = logging.getLogger(__name__) + + +class PlaybackController: + def __init__(self, audio, backends, core): + # TODO: these should be internal + self.backends = backends + self.core = core + self._audio = audio + + self._stream_title = None + self._state = PlaybackState.STOPPED + + self._current_tl_track = None + self._pending_tl_track = None + + self._pending_position = None + self._last_position = None + self._previous = False + + self._start_at_position = None + self._start_paused = False + + if self._audio: + self._audio.set_about_to_finish_callback( + self._on_about_to_finish_callback + ) + + def _get_backend(self, tl_track): + if tl_track is None: + return None + uri_scheme = urllib.parse.urlparse(tl_track.track.uri).scheme + return self.backends.with_playback.get(uri_scheme, None) + + def get_current_tl_track(self): + """Get the currently playing or selected track. + + Returns a :class:`mopidy.models.TlTrack` or :class:`None`. + """ + return self._current_tl_track + + def _set_current_tl_track(self, value): + """Set the currently playing or selected track. + + *Internal:* This is only for use by Mopidy's test suite. + """ + self._current_tl_track = value + + def get_current_track(self): + """ + Get the currently playing or selected track. + + Extracted from :meth:`get_current_tl_track` for convenience. + + Returns a :class:`mopidy.models.Track` or :class:`None`. + """ + return getattr(self.get_current_tl_track(), "track", None) + + def get_current_tlid(self): + """ + Get the currently playing or selected TLID. + + Extracted from :meth:`get_current_tl_track` for convenience. + + Returns a :class:`int` or :class:`None`. + + .. versionadded:: 1.1 + """ + return getattr(self.get_current_tl_track(), "tlid", None) + + def get_stream_title(self): + """Get the current stream title or :class:`None`.""" + return self._stream_title + + def get_state(self): + """Get The playback state.""" + + return self._state + + def set_state(self, new_state): + """Set the playback state. + + Must be :attr:`PLAYING`, :attr:`PAUSED`, or :attr:`STOPPED`. + + Possible states and transitions: + + .. digraph:: state_transitions + + "STOPPED" -> "PLAYING" [ label="play" ] + "STOPPED" -> "PAUSED" [ label="pause" ] + "PLAYING" -> "STOPPED" [ label="stop" ] + "PLAYING" -> "PAUSED" [ label="pause" ] + "PLAYING" -> "PLAYING" [ label="play" ] + "PAUSED" -> "PLAYING" [ label="resume" ] + "PAUSED" -> "STOPPED" [ label="stop" ] + """ + validation.check_choice(new_state, validation.PLAYBACK_STATES) + + (old_state, self._state) = (self.get_state(), new_state) + logger.debug("Changing state: %s -> %s", old_state, new_state) + + self._trigger_playback_state_changed(old_state, new_state) + + def get_time_position(self): + """Get time position in milliseconds.""" + if self._pending_position is not None: + return self._pending_position + backend = self._get_backend(self.get_current_tl_track()) + if backend: + # TODO: Wrap backend call in error handling. + return backend.playback.get_time_position().get() + else: + return 0 + + def _on_end_of_stream(self): + self.set_state(PlaybackState.STOPPED) + if self._current_tl_track: + self._trigger_track_playback_ended(self.get_time_position()) + self._set_current_tl_track(None) + + def _on_stream_changed(self, uri): + if self._last_position is None: + position = self.get_time_position() + else: + # This code path handles the stop() case, uri should be none. + position, self._last_position = self._last_position, None + + if self._pending_position is None: + self._trigger_track_playback_ended(position) + + self._stream_title = None + if self._pending_tl_track: + self._set_current_tl_track(self._pending_tl_track) + self._pending_tl_track = None + + if self._pending_position is None: + self.set_state(PlaybackState.PLAYING) + self._trigger_track_playback_started() + seek_ok = False + if self._start_at_position: + seek_ok = self.seek(self._start_at_position) + self._start_at_position = None + if not seek_ok and self._start_paused: + self.pause() + self._start_paused = False + else: + self._seek(self._pending_position) + + def _on_position_changed(self, position): + if self._pending_position is not None: + self._trigger_seeked(self._pending_position) + self._pending_position = None + if self._start_paused: + self._start_paused = False + self.pause() + + def _on_about_to_finish_callback(self): + """Callback that performs a blocking actor call to the real callback. + + This is passed to audio, which is allowed to call this code from the + audio thread. We pass execution into the core actor to ensure that + there is no unsafe access of state in core. This must block until + we get a response. + """ + self.core.actor_ref.ask( + ProxyCall( + attr_path=["playback", "_on_about_to_finish"], + args=[], + kwargs={}, + ) + ) + + def _on_about_to_finish(self): + if self._state == PlaybackState.STOPPED: + return + + # Unless overridden by other calls (e.g. next / previous / stop) this + # will be the last position recorded until the track gets reassigned. + # TODO: Check if case when track.length isn't populated needs to be + # handled. + self._last_position = self._current_tl_track.track.length + + pending = self.core.tracklist.eot_track(self._current_tl_track) + # avoid endless loop if 'repeat' is 'true' and no track is playable + # * 2 -> second run to get all playable track in a shuffled playlist + count = self.core.tracklist.get_length() * 2 + + while pending: + backend = self._get_backend(pending) + if backend: + try: + if backend.playback.change_track(pending.track).get(): + self._pending_tl_track = pending + break + except Exception: + logger.exception( + "%s backend caused an exception.", + backend.actor_ref.actor_class.__name__, + ) + + self.core.tracklist._mark_unplayable(pending) + pending = self.core.tracklist.eot_track(pending) + count -= 1 + if not count: + logger.info("No playable track in the list.") + break + + def _on_tracklist_change(self): + """ + Tell the playback controller that the current playlist has changed. + + Used by :class:`mopidy.core.TracklistController`. + """ + tl_tracks = self.core.tracklist.get_tl_tracks() + if not tl_tracks: + self.stop() + self._set_current_tl_track(None) + elif self.get_current_tl_track() not in tl_tracks: + self._set_current_tl_track(None) + + def next(self): + """ + Change to the next track. + + The current playback state will be kept. If it was playing, playing + will continue. If it was paused, it will still be paused, etc. + """ + state = self.get_state() + current = self._pending_tl_track or self._current_tl_track + # avoid endless loop if 'repeat' is 'true' and no track is playable + # * 2 -> second run to get all playable track in a shuffled playlist + count = self.core.tracklist.get_length() * 2 + + while current: + pending = self.core.tracklist.next_track(current) + if self._change(pending, state): + break + else: + self.core.tracklist._mark_unplayable(pending) + # TODO: this could be needed to prevent a loop in rare cases + # if current == pending: + # break + current = pending + count -= 1 + if not count: + logger.info("No playable track in the list.") + break + + # TODO return result? + + def pause(self): + """Pause playback.""" + backend = self._get_backend(self.get_current_tl_track()) + # TODO: Wrap backend call in error handling. + if not backend or backend.playback.pause().get(): + # TODO: switch to: + # backend.track(pause) + # wait for state change? + self.set_state(PlaybackState.PAUSED) + self._trigger_track_playback_paused() + + def play(self, tl_track=None, tlid=None): + """ + Play the given track, or if the given tl_track and tlid is + :class:`None`, play the currently active track. + + Note that the track **must** already be in the tracklist. + + .. deprecated:: 3.0 + The ``tl_track`` argument. Use ``tlid`` instead. + + :param tl_track: track to play + :type tl_track: :class:`mopidy.models.TlTrack` or :class:`None` + :param tlid: TLID of the track to play + :type tlid: :class:`int` or :class:`None` + """ + if sum(o is not None for o in [tl_track, tlid]) > 1: + raise ValueError('At most one of "tl_track" and "tlid" may be set') + + tl_track is None or validation.check_instance(tl_track, models.TlTrack) + tlid is None or validation.check_integer(tlid, min=1) + + if tl_track: + deprecation.warn("core.playback.play:tl_track_kwarg") + + if tl_track is None and tlid is not None: + for tl_track in self.core.tracklist.get_tl_tracks(): + if tl_track.tlid == tlid: + break + else: + tl_track = None + + if tl_track is not None: + # TODO: allow from outside tracklist, would make sense given refs? + assert tl_track in self.core.tracklist.get_tl_tracks() + elif tl_track is None and self.get_state() == PlaybackState.PAUSED: + self.resume() + return + + current = self._pending_tl_track or self._current_tl_track + pending = tl_track or current or self.core.tracklist.next_track(None) + # avoid endless loop if 'repeat' is 'true' and no track is playable + # * 2 -> second run to get all playable track in a shuffled playlist + count = self.core.tracklist.get_length() * 2 + + while pending: + if self._change(pending, PlaybackState.PLAYING): + break + else: + self.core.tracklist._mark_unplayable(pending) + current = pending + pending = self.core.tracklist.next_track(current) + count -= 1 + if not count: + logger.info("No playable track in the list.") + break + + # TODO return result? + + def _change(self, pending_tl_track, state): + self._pending_tl_track = pending_tl_track + + if not pending_tl_track: + self.stop() + self._on_end_of_stream() # pretend an EOS happened for cleanup + return True + + backend = self._get_backend(pending_tl_track) + if not backend: + return False + + # This must happen before prepare_change gets called, otherwise the + # backend flushes the information of the track. + self._last_position = self.get_time_position() + + # TODO: Wrap backend call in error handling. + backend.playback.prepare_change() + + try: + if not backend.playback.change_track(pending_tl_track.track).get(): + return False + except Exception: + logger.exception( + "%s backend caused an exception.", + backend.actor_ref.actor_class.__name__, + ) + return False + + # TODO: Wrap backend calls in error handling. + if state == PlaybackState.PLAYING: + try: + return backend.playback.play().get() + except TypeError: + # TODO: check by binding against underlying play method using + # inspect and otherwise re-raise? + logger.error( + "%s needs to be updated to work with this " + "version of Mopidy.", + backend, + ) + return False + elif state == PlaybackState.PAUSED: + return backend.playback.pause().get() + elif state == PlaybackState.STOPPED: + # TODO: emit some event now? + self._current_tl_track = self._pending_tl_track + self._pending_tl_track = None + return True + + raise Exception(f"Unknown state: {state}") + + def previous(self): + """ + Change to the previous track. + + The current playback state will be kept. If it was playing, playing + will continue. If it was paused, it will still be paused, etc. + """ + self._previous = True + state = self.get_state() + current = self._pending_tl_track or self._current_tl_track + # avoid endless loop if 'repeat' is 'true' and no track is playable + # * 2 -> second run to get all playable track in a shuffled playlist + count = self.core.tracklist.get_length() * 2 + + while current: + pending = self.core.tracklist.previous_track(current) + if self._change(pending, state): + break + else: + self.core.tracklist._mark_unplayable(pending) + # TODO: this could be needed to prevent a loop in rare cases + # if current == pending: + # break + current = pending + count -= 1 + if not count: + logger.info("No playable track in the list.") + break + + # TODO: no return value? + + def resume(self): + """If paused, resume playing the current track.""" + if self.get_state() != PlaybackState.PAUSED: + return + backend = self._get_backend(self.get_current_tl_track()) + # TODO: Wrap backend call in error handling. + if backend and backend.playback.resume().get(): + self.set_state(PlaybackState.PLAYING) + # TODO: trigger via gst messages + self._trigger_track_playback_resumed() + # TODO: switch to: + # backend.resume() + # wait for state change? + + def seek(self, time_position): + """ + Seeks to time position given in milliseconds. + + :param time_position: time position in milliseconds + :type time_position: int + :rtype: :class:`True` if successful, else :class:`False` + """ + # TODO: seek needs to take pending tracks into account :( + validation.check_integer(time_position) + + if time_position < 0: + logger.debug("Client seeked to negative position. Seeking to zero.") + time_position = 0 + + if not self.core.tracklist.get_length(): + return False + + if self.get_state() == PlaybackState.STOPPED: + self.play() + + # We need to prefer the still playing track, but if nothing is playing + # we fall back to the pending one. + tl_track = self._current_tl_track or self._pending_tl_track + if tl_track and tl_track.track.length is None: + return False + + if time_position < 0: + time_position = 0 + elif time_position > tl_track.track.length: + # TODO: GStreamer will trigger a about-to-finish for us, use that? + self.next() + return True + + # Store our target position. + self._pending_position = time_position + + # Make sure we switch back to previous track if we get a seek while we + # have a pending track. + if self._current_tl_track and self._pending_tl_track: + self._change(self._current_tl_track, self.get_state()) + else: + return self._seek(time_position) + + def _seek(self, time_position): + backend = self._get_backend(self.get_current_tl_track()) + if not backend: + return False + # TODO: Wrap backend call in error handling. + return backend.playback.seek(time_position).get() + + def stop(self): + """Stop playing.""" + if self.get_state() != PlaybackState.STOPPED: + self._last_position = self.get_time_position() + backend = self._get_backend(self.get_current_tl_track()) + # TODO: Wrap backend call in error handling. + if not backend or backend.playback.stop().get(): + self.set_state(PlaybackState.STOPPED) + + def _trigger_track_playback_paused(self): + logger.debug("Triggering track playback paused event") + if self.get_current_tl_track() is None: + return + listener.CoreListener.send( + "track_playback_paused", + tl_track=self.get_current_tl_track(), + time_position=self.get_time_position(), + ) + + def _trigger_track_playback_resumed(self): + logger.debug("Triggering track playback resumed event") + if self.get_current_tl_track() is None: + return + listener.CoreListener.send( + "track_playback_resumed", + tl_track=self.get_current_tl_track(), + time_position=self.get_time_position(), + ) + + def _trigger_track_playback_started(self): + if self.get_current_tl_track() is None: + return + + logger.debug("Triggering track playback started event") + tl_track = self.get_current_tl_track() + self.core.tracklist._mark_playing(tl_track) + self.core.history._add_track(tl_track.track) + listener.CoreListener.send("track_playback_started", tl_track=tl_track) + + def _trigger_track_playback_ended(self, time_position_before_stop): + tl_track = self.get_current_tl_track() + if tl_track is None: + return + + logger.debug("Triggering track playback ended event") + + if not self._previous: + self.core.tracklist._mark_played(self._current_tl_track) + self._previous = False + + # TODO: Use the lowest of track duration and position. + listener.CoreListener.send( + "track_playback_ended", + tl_track=tl_track, + time_position=time_position_before_stop, + ) + + def _trigger_playback_state_changed(self, old_state, new_state): + logger.debug("Triggering playback state change event") + listener.CoreListener.send( + "playback_state_changed", old_state=old_state, new_state=new_state + ) + + def _trigger_seeked(self, time_position): + # TODO: Trigger this from audio events? + logger.debug("Triggering seeked event") + listener.CoreListener.send("seeked", time_position=time_position) + + def _save_state(self): + return models.PlaybackState( + tlid=self.get_current_tlid(), + time_position=self.get_time_position(), + state=self.get_state(), + ) + + def _load_state(self, state, coverage): + if state and "play-last" in coverage and state.tlid is not None: + if state.state == PlaybackState.PAUSED: + self._start_paused = True + if state.state in (PlaybackState.PLAYING, PlaybackState.PAUSED): + self._start_at_position = state.time_position + self.play(tlid=state.tlid) diff --git a/venv/lib/python3.7/site-packages/mopidy/core/playlists.py b/venv/lib/python3.7/site-packages/mopidy/core/playlists.py new file mode 100644 index 0000000..5dff83c --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/playlists.py @@ -0,0 +1,280 @@ +import contextlib +import logging +import urllib + +from mopidy import exceptions +from mopidy.core import listener +from mopidy.internal import validation +from mopidy.models import Playlist, Ref + +logger = logging.getLogger(__name__) + + +@contextlib.contextmanager +def _backend_error_handling(backend, reraise=None): + try: + yield + except exceptions.ValidationError as e: + logger.error( + "%s backend returned bad data: %s", + backend.actor_ref.actor_class.__name__, + e, + ) + except Exception as e: + if reraise and isinstance(e, reraise): + raise + logger.exception( + "%s backend caused an exception.", + backend.actor_ref.actor_class.__name__, + ) + + +class PlaylistsController: + def __init__(self, backends, core): + self.backends = backends + self.core = core + + def get_uri_schemes(self): + """ + Get the list of URI schemes that support playlists. + + :rtype: list of string + + .. versionadded:: 2.0 + """ + return list(sorted(self.backends.with_playlists.keys())) + + def as_list(self): + """ + Get a list of the currently available playlists. + + Returns a list of :class:`~mopidy.models.Ref` objects referring to the + playlists. In other words, no information about the playlists' content + is given. + + :rtype: list of :class:`mopidy.models.Ref` + + .. versionadded:: 1.0 + """ + futures = { + backend: backend.playlists.as_list() + for backend in set(self.backends.with_playlists.values()) + } + + results = [] + for b, future in futures.items(): + try: + with _backend_error_handling(b, reraise=NotImplementedError): + playlists = future.get() + if playlists is not None: + validation.check_instances(playlists, Ref) + results.extend(playlists) + except NotImplementedError: + backend_name = b.actor_ref.actor_class.__name__ + logger.warning( + "%s does not implement playlists.as_list(). " + "Please upgrade it.", + backend_name, + ) + + return results + + def get_items(self, uri): + """ + Get the items in a playlist specified by ``uri``. + + Returns a list of :class:`~mopidy.models.Ref` objects referring to the + playlist's items. + + If a playlist with the given ``uri`` doesn't exist, it returns + :class:`None`. + + :rtype: list of :class:`mopidy.models.Ref`, or :class:`None` + + .. versionadded:: 1.0 + """ + validation.check_uri(uri) + + uri_scheme = urllib.parse.urlparse(uri).scheme + backend = self.backends.with_playlists.get(uri_scheme, None) + + if not backend: + return None + + with _backend_error_handling(backend): + items = backend.playlists.get_items(uri).get() + items is None or validation.check_instances(items, Ref) + return items + + return None + + def create(self, name, uri_scheme=None): + """ + Create a new playlist. + + If ``uri_scheme`` matches an URI scheme handled by a current backend, + that backend is asked to create the playlist. If ``uri_scheme`` is + :class:`None` or doesn't match a current backend, the first backend is + asked to create the playlist. + + All new playlists must be created by calling this method, and **not** + by creating new instances of :class:`mopidy.models.Playlist`. + + :param name: name of the new playlist + :type name: string + :param uri_scheme: use the backend matching the URI scheme + :type uri_scheme: string + :rtype: :class:`mopidy.models.Playlist` or :class:`None` + """ + if uri_scheme in self.backends.with_playlists: + backends = [self.backends.with_playlists[uri_scheme]] + else: + backends = self.backends.with_playlists.values() + + for backend in backends: + with _backend_error_handling(backend): + result = backend.playlists.create(name).get() + if result is None: + continue + validation.check_instance(result, Playlist) + listener.CoreListener.send("playlist_changed", playlist=result) + return result + + return None + + def delete(self, uri): + """ + Delete playlist identified by the URI. + + If the URI doesn't match the URI schemes handled by the current + backends, nothing happens. + + Returns :class:`True` if deleted, :class:`False` otherwise. + + :param uri: URI of the playlist to delete + :type uri: string + :rtype: :class:`bool` + + .. versionchanged:: 2.2 + Return type defined. + """ + validation.check_uri(uri) + + uri_scheme = urllib.parse.urlparse(uri).scheme + backend = self.backends.with_playlists.get(uri_scheme, None) + if not backend: + return False + + success = False + with _backend_error_handling(backend): + success = backend.playlists.delete(uri).get() + + if success is None: + # Return type was defined in Mopidy 2.2. Assume everything went + # well if the backend doesn't report otherwise. + success = True + + if success: + listener.CoreListener.send("playlist_deleted", uri=uri) + + return success + + def lookup(self, uri): + """ + Lookup playlist with given URI in both the set of playlists and in any + other playlist sources. Returns :class:`None` if not found. + + :param uri: playlist URI + :type uri: string + :rtype: :class:`mopidy.models.Playlist` or :class:`None` + """ + uri_scheme = urllib.parse.urlparse(uri).scheme + backend = self.backends.with_playlists.get(uri_scheme, None) + if not backend: + return None + + with _backend_error_handling(backend): + playlist = backend.playlists.lookup(uri).get() + playlist is None or validation.check_instance(playlist, Playlist) + return playlist + + return None + + # TODO: there is an inconsistency between library.refresh(uri) and this + # call, not sure how to sort this out. + def refresh(self, uri_scheme=None): + """ + Refresh the playlists in :attr:`playlists`. + + If ``uri_scheme`` is :class:`None`, all backends are asked to refresh. + If ``uri_scheme`` is an URI scheme handled by a backend, only that + backend is asked to refresh. If ``uri_scheme`` doesn't match any + current backend, nothing happens. + + :param uri_scheme: limit to the backend matching the URI scheme + :type uri_scheme: string + """ + # TODO: check: uri_scheme is None or uri_scheme? + + futures = {} + backends = {} + playlists_loaded = False + + for backend_scheme, backend in self.backends.with_playlists.items(): + backends.setdefault(backend, set()).add(backend_scheme) + + for backend, backend_schemes in backends.items(): + if uri_scheme is None or uri_scheme in backend_schemes: + futures[backend] = backend.playlists.refresh() + + for backend, future in futures.items(): + with _backend_error_handling(backend): + future.get() + playlists_loaded = True + + if playlists_loaded: + listener.CoreListener.send("playlists_loaded") + + def save(self, playlist): + """ + Save the playlist. + + For a playlist to be saveable, it must have the ``uri`` attribute set. + You must not set the ``uri`` atribute yourself, but use playlist + objects returned by :meth:`create` or retrieved from :attr:`playlists`, + which will always give you saveable playlists. + + The method returns the saved playlist. The return playlist may differ + from the saved playlist. E.g. if the playlist name was changed, the + returned playlist may have a different URI. The caller of this method + must throw away the playlist sent to this method, and use the + returned playlist instead. + + If the playlist's URI isn't set or doesn't match the URI scheme of a + current backend, nothing is done and :class:`None` is returned. + + :param playlist: the playlist + :type playlist: :class:`mopidy.models.Playlist` + :rtype: :class:`mopidy.models.Playlist` or :class:`None` + """ + validation.check_instance(playlist, Playlist) + + if playlist.uri is None: + return # TODO: log this problem? + + uri_scheme = urllib.parse.urlparse(playlist.uri).scheme + backend = self.backends.with_playlists.get(uri_scheme, None) + if not backend: + return None + + # TODO: we let AssertionError error through due to legacy tests :/ + with _backend_error_handling(backend, reraise=AssertionError): + playlist = backend.playlists.save(playlist).get() + playlist is None or validation.check_instance(playlist, Playlist) + if playlist: + listener.CoreListener.send( + "playlist_changed", playlist=playlist + ) + return playlist + + return None diff --git a/venv/lib/python3.7/site-packages/mopidy/core/tracklist.py b/venv/lib/python3.7/site-packages/mopidy/core/tracklist.py new file mode 100644 index 0000000..1afd37e --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/core/tracklist.py @@ -0,0 +1,620 @@ +import logging +import random + +from mopidy import exceptions +from mopidy.core import listener +from mopidy.internal import deprecation, validation +from mopidy.internal.models import TracklistState +from mopidy.models import TlTrack, Track + +logger = logging.getLogger(__name__) + + +class TracklistController: + def __init__(self, core): + self.core = core + self._next_tlid = 1 + self._tl_tracks = [] + self._version = 0 + + self._consume = False + self._random = False + self._shuffled = [] + self._repeat = False + self._single = False + + def get_tl_tracks(self): + """Get tracklist as list of :class:`mopidy.models.TlTrack`.""" + return self._tl_tracks[:] + + def get_tracks(self): + """Get tracklist as list of :class:`mopidy.models.Track`.""" + return [tl_track.track for tl_track in self._tl_tracks] + + def get_length(self): + """Get length of the tracklist.""" + return len(self._tl_tracks) + + def get_version(self): + """ + Get the tracklist version. + + Integer which is increased every time the tracklist is changed. Is not + reset before Mopidy is restarted. + """ + return self._version + + def _increase_version(self): + self._version += 1 + self.core.playback._on_tracklist_change() + self._trigger_tracklist_changed() + + def get_consume(self): + """Get consume mode. + + :class:`True` + Tracks are removed from the tracklist when they have been played. + :class:`False` + Tracks are not removed from the tracklist. + """ + return self._consume + + def set_consume(self, value): + """Set consume mode. + + :class:`True` + Tracks are removed from the tracklist when they have been played. + :class:`False` + Tracks are not removed from the tracklist. + """ + validation.check_boolean(value) + if self.get_consume() != value: + self._trigger_options_changed() + self._consume = value + + def get_random(self): + """Get random mode. + + :class:`True` + Tracks are selected at random from the tracklist. + :class:`False` + Tracks are played in the order of the tracklist. + """ + return self._random + + def set_random(self, value): + """Set random mode. + + :class:`True` + Tracks are selected at random from the tracklist. + :class:`False` + Tracks are played in the order of the tracklist. + """ + validation.check_boolean(value) + if self.get_random() != value: + self._trigger_options_changed() + if value: + self._shuffled = self.get_tl_tracks() + random.shuffle(self._shuffled) + self._random = value + + def get_repeat(self): + """ + Get repeat mode. + + :class:`True` + The tracklist is played repeatedly. + :class:`False` + The tracklist is played once. + """ + return self._repeat + + def set_repeat(self, value): + """ + Set repeat mode. + + To repeat a single track, set both ``repeat`` and ``single``. + + :class:`True` + The tracklist is played repeatedly. + :class:`False` + The tracklist is played once. + """ + validation.check_boolean(value) + if self.get_repeat() != value: + self._trigger_options_changed() + self._repeat = value + + def get_single(self): + """ + Get single mode. + + :class:`True` + Playback is stopped after current song, unless in ``repeat`` mode. + :class:`False` + Playback continues after current song. + """ + return self._single + + def set_single(self, value): + """ + Set single mode. + + :class:`True` + Playback is stopped after current song, unless in ``repeat`` mode. + :class:`False` + Playback continues after current song. + """ + validation.check_boolean(value) + if self.get_single() != value: + self._trigger_options_changed() + self._single = value + + def index(self, tl_track=None, tlid=None): + """ + The position of the given track in the tracklist. + + If neither *tl_track* or *tlid* is given we return the index of + the currently playing track. + + :param tl_track: the track to find the index of + :type tl_track: :class:`mopidy.models.TlTrack` or :class:`None` + :param tlid: TLID of the track to find the index of + :type tlid: :class:`int` or :class:`None` + :rtype: :class:`int` or :class:`None` + + .. versionadded:: 1.1 + The *tlid* parameter + """ + tl_track is None or validation.check_instance(tl_track, TlTrack) + tlid is None or validation.check_integer(tlid, min=1) + + if tl_track is None and tlid is None: + tl_track = self.core.playback.get_current_tl_track() + + if tl_track is not None: + try: + return self._tl_tracks.index(tl_track) + except ValueError: + pass + elif tlid is not None: + for i, tl_track in enumerate(self._tl_tracks): + if tl_track.tlid == tlid: + return i + return None + + def get_eot_tlid(self): + """ + The TLID of the track that will be played after the current track. + + Not necessarily the same TLID as returned by :meth:`get_next_tlid`. + + :rtype: :class:`int` or :class:`None` + + .. versionadded:: 1.1 + """ + + current_tl_track = self.core.playback.get_current_tl_track() + + with deprecation.ignore("core.tracklist.eot_track"): + eot_tl_track = self.eot_track(current_tl_track) + + return getattr(eot_tl_track, "tlid", None) + + def eot_track(self, tl_track): + """ + The track that will be played after the given track. + + Not necessarily the same track as :meth:`next_track`. + + .. deprecated:: 3.0 + Use :meth:`get_eot_tlid` instead. + + :param tl_track: the reference track + :type tl_track: :class:`mopidy.models.TlTrack` or :class:`None` + :rtype: :class:`mopidy.models.TlTrack` or :class:`None` + """ + deprecation.warn("core.tracklist.eot_track") + tl_track is None or validation.check_instance(tl_track, TlTrack) + if self.get_single() and self.get_repeat(): + return tl_track + elif self.get_single(): + return None + + # Current difference between next and EOT handling is that EOT needs to + # handle "single", with that out of the way the rest of the logic is + # shared. + return self.next_track(tl_track) + + def get_next_tlid(self): + """ + The tlid of the track that will be played if calling + :meth:`mopidy.core.PlaybackController.next()`. + + For normal playback this is the next track in the tracklist. If repeat + is enabled the next track can loop around the tracklist. When random is + enabled this should be a random track, all tracks should be played once + before the tracklist repeats. + + :rtype: :class:`int` or :class:`None` + + .. versionadded:: 1.1 + """ + current_tl_track = self.core.playback.get_current_tl_track() + + with deprecation.ignore("core.tracklist.next_track"): + next_tl_track = self.next_track(current_tl_track) + + return getattr(next_tl_track, "tlid", None) + + def next_track(self, tl_track): + """ + The track that will be played if calling + :meth:`mopidy.core.PlaybackController.next()`. + + For normal playback this is the next track in the tracklist. If repeat + is enabled the next track can loop around the tracklist. When random is + enabled this should be a random track, all tracks should be played once + before the tracklist repeats. + + .. deprecated:: 3.0 + Use :meth:`get_next_tlid` instead. + + :param tl_track: the reference track + :type tl_track: :class:`mopidy.models.TlTrack` or :class:`None` + :rtype: :class:`mopidy.models.TlTrack` or :class:`None` + """ + deprecation.warn("core.tracklist.next_track") + tl_track is None or validation.check_instance(tl_track, TlTrack) + + if not self._tl_tracks: + return None + + if self.get_random() and not self._shuffled: + if self.get_repeat() or not tl_track: + logger.debug("Shuffling tracks") + self._shuffled = self._tl_tracks[:] + random.shuffle(self._shuffled) + + if self.get_random(): + if self._shuffled: + return self._shuffled[0] + return None + + next_index = self.index(tl_track) + if next_index is None: + next_index = 0 + else: + next_index += 1 + + if self.get_repeat(): + if self.get_consume() and len(self._tl_tracks) == 1: + return None + else: + next_index %= len(self._tl_tracks) + elif next_index >= len(self._tl_tracks): + return None + + return self._tl_tracks[next_index] + + def get_previous_tlid(self): + """ + Returns the TLID of the track that will be played if calling + :meth:`mopidy.core.PlaybackController.previous()`. + + For normal playback this is the previous track in the tracklist. If + random and/or consume is enabled it should return the current track + instead. + + :rtype: :class:`int` or :class:`None` + + .. versionadded:: 1.1 + """ + current_tl_track = self.core.playback.get_current_tl_track() + + with deprecation.ignore("core.tracklist.previous_track"): + previous_tl_track = self.previous_track(current_tl_track) + + return getattr(previous_tl_track, "tlid", None) + + def previous_track(self, tl_track): + """ + Returns the track that will be played if calling + :meth:`mopidy.core.PlaybackController.previous()`. + + For normal playback this is the previous track in the tracklist. If + random and/or consume is enabled it should return the current track + instead. + + .. deprecated:: 3.0 + Use :meth:`get_previous_tlid` instead. + + :param tl_track: the reference track + :type tl_track: :class:`mopidy.models.TlTrack` or :class:`None` + :rtype: :class:`mopidy.models.TlTrack` or :class:`None` + """ + deprecation.warn("core.tracklist.previous_track") + tl_track is None or validation.check_instance(tl_track, TlTrack) + + if self.get_repeat() or self.get_consume() or self.get_random(): + return tl_track + + position = self.index(tl_track) + + if position in (None, 0): + return None + + # Since we know we are not at zero we have to be somewhere in the range + # 1 - len(tracks) Thus 'position - 1' will always be within the list. + return self._tl_tracks[position - 1] + + def add(self, tracks=None, at_position=None, uris=None): + """ + Add tracks to the tracklist. + + If ``uris`` is given instead of ``tracks``, the URIs are + looked up in the library and the resulting tracks are added to the + tracklist. + + If ``at_position`` is given, the tracks are inserted at the given + position in the tracklist. If ``at_position`` is not given, the tracks + are appended to the end of the tracklist. + + Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. + + :param tracks: tracks to add + :type tracks: list of :class:`mopidy.models.Track` or :class:`None` + :param at_position: position in tracklist to add tracks + :type at_position: int or :class:`None` + :param uris: list of URIs for tracks to add + :type uris: list of string or :class:`None` + :rtype: list of :class:`mopidy.models.TlTrack` + + .. versionadded:: 1.0 + The ``uris`` argument. + + .. deprecated:: 1.0 + The ``tracks`` argument. Use ``uris``. + """ + if sum(o is not None for o in [tracks, uris]) != 1: + raise ValueError('Exactly one of "tracks" or "uris" must be set') + + tracks is None or validation.check_instances(tracks, Track) + uris is None or validation.check_uris(uris) + validation.check_integer(at_position or 0) + + if tracks: + deprecation.warn("core.tracklist.add:tracks_arg") + + if tracks is None: + tracks = [] + track_map = self.core.library.lookup(uris=uris) + for uri in uris: + tracks.extend(track_map[uri]) + + tl_tracks = [] + max_length = self.core._config["core"]["max_tracklist_length"] + + for track in tracks: + if self.get_length() >= max_length: + raise exceptions.TracklistFull( + f"Tracklist may contain at most {max_length:d} tracks." + ) + + tl_track = TlTrack(self._next_tlid, track) + self._next_tlid += 1 + if at_position is not None: + self._tl_tracks.insert(at_position, tl_track) + at_position += 1 + else: + self._tl_tracks.append(tl_track) + tl_tracks.append(tl_track) + + if tl_tracks: + self._increase_version() + + return tl_tracks + + def clear(self): + """ + Clear the tracklist. + + Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. + """ + self._tl_tracks = [] + self._increase_version() + + def filter(self, criteria): + """ + Filter the tracklist by the given criteria. + + Each rule in the criteria consists of a model field and a list of + values to compare it against. If the model field matches any of the + values, it may be returned. + + Only tracks that match all the given criteria are returned. + + Examples:: + + # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID) + filter({'tlid': [1, 2, 3, 4]}) + + # Returns track with URIs 'xyz' or 'abc' + filter({'uri': ['xyz', 'abc']}) + + # Returns track with a matching TLIDs (1, 3 or 6) and a + # matching URI ('xyz' or 'abc') + filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']}) + + :param criteria: one or more rules to match by + :type criteria: dict, of (string, list) pairs + :rtype: list of :class:`mopidy.models.TlTrack` + """ + tlids = criteria.pop("tlid", []) + validation.check_query(criteria, validation.TRACKLIST_FIELDS) + validation.check_instances(tlids, int) + + matches = self._tl_tracks + for (key, values) in criteria.items(): + matches = [ct for ct in matches if getattr(ct.track, key) in values] + if tlids: + matches = [ct for ct in matches if ct.tlid in tlids] + return matches + + def move(self, start, end, to_position): + """ + Move the tracks in the slice ``[start:end]`` to ``to_position``. + + Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. + + :param start: position of first track to move + :type start: int + :param end: position after last track to move + :type end: int + :param to_position: new position for the tracks + :type to_position: int + """ + if start == end: + end += 1 + + tl_tracks = self._tl_tracks + + # TODO: use validation helpers? + assert start < end, "start must be smaller than end" + assert start >= 0, "start must be at least zero" + assert end <= len( + tl_tracks + ), "end can not be larger than tracklist length" + assert to_position >= 0, "to_position must be at least zero" + assert to_position <= len( + tl_tracks + ), "to_position can not be larger than tracklist length" + + new_tl_tracks = tl_tracks[:start] + tl_tracks[end:] + for tl_track in tl_tracks[start:end]: + new_tl_tracks.insert(to_position, tl_track) + to_position += 1 + self._tl_tracks = new_tl_tracks + self._increase_version() + + def remove(self, criteria): + """ + Remove the matching tracks from the tracklist. + + Uses :meth:`filter()` to lookup the tracks to remove. + + Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. + + :param criteria: one or more rules to match by + :type criteria: dict, of (string, list) pairs + :rtype: list of :class:`mopidy.models.TlTrack` that were removed + """ + tl_tracks = self.filter(criteria) + for tl_track in tl_tracks: + position = self._tl_tracks.index(tl_track) + del self._tl_tracks[position] + self._increase_version() + return tl_tracks + + def shuffle(self, start=None, end=None): + """ + Shuffles the entire tracklist. If ``start`` and ``end`` is given only + shuffles the slice ``[start:end]``. + + Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event. + + :param start: position of first track to shuffle + :type start: int or :class:`None` + :param end: position after last track to shuffle + :type end: int or :class:`None` + """ + tl_tracks = self._tl_tracks + + # TOOD: use validation helpers? + if start is not None and end is not None: + assert start < end, "start must be smaller than end" + + if start is not None: + assert start >= 0, "start must be at least zero" + + if end is not None: + assert end <= len(tl_tracks), ( + "end can not be larger than " + "tracklist length" + ) + + before = tl_tracks[: start or 0] + shuffled = tl_tracks[start:end] + after = tl_tracks[end or len(tl_tracks) :] + random.shuffle(shuffled) + self._tl_tracks = before + shuffled + after + self._increase_version() + + def slice(self, start, end): + """ + Returns a slice of the tracklist, limited by the given start and end + positions. + + :param start: position of first track to include in slice + :type start: int + :param end: position after last track to include in slice + :type end: int + :rtype: :class:`mopidy.models.TlTrack` + """ + # TODO: validate slice? + return self._tl_tracks[start:end] + + def _mark_playing(self, tl_track): + """Internal method for :class:`mopidy.core.PlaybackController`.""" + if self.get_random() and tl_track in self._shuffled: + self._shuffled.remove(tl_track) + + def _mark_unplayable(self, tl_track): + """Internal method for :class:`mopidy.core.PlaybackController`.""" + logger.warning("Track is not playable: %s", tl_track.track.uri) + if self.get_consume() and tl_track is not None: + self.remove({"tlid": [tl_track.tlid]}) + if self.get_random() and tl_track in self._shuffled: + self._shuffled.remove(tl_track) + + def _mark_played(self, tl_track): + """Internal method for :class:`mopidy.core.PlaybackController`.""" + if self.get_consume() and tl_track is not None: + self.remove({"tlid": [tl_track.tlid]}) + return True + return False + + def _trigger_tracklist_changed(self): + if self.get_random(): + self._shuffled = self._tl_tracks[:] + random.shuffle(self._shuffled) + else: + self._shuffled = [] + + logger.debug("Triggering event: tracklist_changed()") + listener.CoreListener.send("tracklist_changed") + + def _trigger_options_changed(self): + logger.debug("Triggering options changed event") + listener.CoreListener.send("options_changed") + + def _save_state(self): + return TracklistState( + tl_tracks=self._tl_tracks, + next_tlid=self._next_tlid, + consume=self.get_consume(), + random=self.get_random(), + repeat=self.get_repeat(), + single=self.get_single(), + ) + + def _load_state(self, state, coverage): + if state: + if "mode" in coverage: + self.set_consume(state.consume) + self.set_random(state.random) + self.set_repeat(state.repeat) + self.set_single(state.single) + if "tracklist" in coverage: + self._next_tlid = max(state.next_tlid, self._next_tlid) + self._tl_tracks = list(state.tl_tracks) + self._increase_version() diff --git a/venv/lib/python3.7/site-packages/mopidy/exceptions.py b/venv/lib/python3.7/site-packages/mopidy/exceptions.py new file mode 100644 index 0000000..332f36e --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/exceptions.py @@ -0,0 +1,53 @@ +class MopidyException(Exception): + def __init__(self, message, *args, **kwargs): + super().__init__(message, *args, **kwargs) + self._message = message + + @property + def message(self): + """Reimplement message field that was deprecated in Python 2.6""" + return self._message + + @message.setter # noqa + def message(self, message): + self._message = message + + +class BackendError(MopidyException): + pass + + +class CoreError(MopidyException): + def __init__(self, message, errno=None): + super().__init__(message, errno) + self.errno = errno + + +class ExtensionError(MopidyException): + pass + + +class FrontendError(MopidyException): + pass + + +class MixerError(MopidyException): + pass + + +class ScannerError(MopidyException): + pass + + +class TracklistFull(CoreError): + def __init__(self, message, errno=None): + super().__init__(message, errno) + self.errno = errno + + +class AudioException(MopidyException): + pass + + +class ValidationError(ValueError): + pass diff --git a/venv/lib/python3.7/site-packages/mopidy/ext.py b/venv/lib/python3.7/site-packages/mopidy/ext.py new file mode 100644 index 0000000..1b4745d --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/ext.py @@ -0,0 +1,340 @@ +import collections +import logging +from collections.abc import Mapping + +import pkg_resources + +from mopidy import config as config_lib +from mopidy import exceptions +from mopidy.internal import path + +logger = logging.getLogger(__name__) + + +_extension_data_fields = [ + "extension", + "entry_point", + "config_schema", + "config_defaults", + "command", +] + +ExtensionData = collections.namedtuple("ExtensionData", _extension_data_fields) + + +class Extension: + + """Base class for Mopidy extensions""" + + dist_name = None + """The extension's distribution name, as registered on PyPI + + Example: ``Mopidy-Soundspot`` + """ + + ext_name = None + """The extension's short name, as used in setup.py and as config section + name + + Example: ``soundspot`` + """ + + version = None + """The extension's version + + Should match the :attr:`__version__` attribute on the extension's main + Python module and the version registered on PyPI. + """ + + def get_default_config(self): + """The extension's default config as a bytestring + + :returns: bytes or unicode + """ + raise NotImplementedError( + 'Add at least a config section with "enabled = true"' + ) + + def get_config_schema(self): + """The extension's config validation schema + + :returns: :class:`~mopidy.config.schemas.ConfigSchema` + """ + schema = config_lib.ConfigSchema(self.ext_name) + schema["enabled"] = config_lib.Boolean() + return schema + + @classmethod + def get_cache_dir(cls, config): + """Get or create cache directory for the extension. + + Use this directory to cache data that can safely be thrown away. + + :param config: the Mopidy config object + :return: pathlib.Path + """ + assert cls.ext_name is not None + cache_dir_path = ( + path.expand_path(config["core"]["cache_dir"]) / cls.ext_name + ) + path.get_or_create_dir(cache_dir_path) + return cache_dir_path + + @classmethod + def get_config_dir(cls, config): + """Get or create configuration directory for the extension. + + :param config: the Mopidy config object + :return: pathlib.Path + """ + assert cls.ext_name is not None + config_dir_path = ( + path.expand_path(config["core"]["config_dir"]) / cls.ext_name + ) + path.get_or_create_dir(config_dir_path) + return config_dir_path + + @classmethod + def get_data_dir(cls, config): + """Get or create data directory for the extension. + + Use this directory to store data that should be persistent. + + :param config: the Mopidy config object + :returns: pathlib.Path + """ + assert cls.ext_name is not None + data_dir_path = ( + path.expand_path(config["core"]["data_dir"]) / cls.ext_name + ) + path.get_or_create_dir(data_dir_path) + return data_dir_path + + def get_command(self): + """Command to expose to command line users running ``mopidy``. + + :returns: + Instance of a :class:`~mopidy.commands.Command` class. + """ + pass + + def validate_environment(self): + """Checks if the extension can run in the current environment. + + Dependencies described by :file:`setup.py` are checked by Mopidy, so + you should not check their presence here. + + If a problem is found, raise :exc:`~mopidy.exceptions.ExtensionError` + with a message explaining the issue. + + :raises: :exc:`~mopidy.exceptions.ExtensionError` + :returns: :class:`None` + """ + pass + + def setup(self, registry): + """ + Register the extension's components in the extension :class:`Registry`. + + For example, to register a backend:: + + def setup(self, registry): + from .backend import SoundspotBackend + registry.add('backend', SoundspotBackend) + + See :class:`Registry` for a list of registry keys with a special + meaning. Mopidy will instantiate and start any classes registered under + the ``frontend`` and ``backend`` registry keys. + + This method can also be used for other setup tasks not involving the + extension registry. + + :param registry: the extension registry + :type registry: :class:`Registry` + """ + raise NotImplementedError + + +class Registry(Mapping): + + """Registry of components provided by Mopidy extensions. + + Passed to the :meth:`~Extension.setup` method of all extensions. The + registry can be used like a dict of string keys and lists. + + Some keys have a special meaning, including, but not limited to: + + - ``backend`` is used for Mopidy backend classes. + - ``frontend`` is used for Mopidy frontend classes. + + Extensions can use the registry for allow other to extend the extension + itself. For example the ``Mopidy-Local`` historically used the + ``local:library`` key to allow other extensions to register library + providers for ``Mopidy-Local`` to use. Extensions should namespace + custom keys with the extension's :attr:`~Extension.ext_name`, + e.g. ``local:foo`` or ``http:bar``. + """ + + def __init__(self): + self._registry = {} + + def add(self, name, cls): + """Add a component to the registry. + + Multiple classes can be registered to the same name. + """ + self._registry.setdefault(name, []).append(cls) + + def __getitem__(self, name): + return self._registry.setdefault(name, []) + + def __iter__(self): + return iter(self._registry) + + def __len__(self): + return len(self._registry) + + +def load_extensions(): + """Find all installed extensions. + + :returns: list of installed extensions + """ + + installed_extensions = [] + + for entry_point in pkg_resources.iter_entry_points("mopidy.ext"): + logger.debug("Loading entry point: %s", entry_point) + try: + extension_class = entry_point.resolve() + except Exception as e: + logger.exception( + f"Failed to load extension {entry_point.name}: {e}" + ) + continue + + try: + if not issubclass(extension_class, Extension): + raise TypeError # issubclass raises TypeError on non-class + except TypeError: + logger.error( + "Entry point %s did not contain a valid extension" "class: %r", + entry_point.name, + extension_class, + ) + continue + + try: + extension = extension_class() + config_schema = extension.get_config_schema() + default_config = extension.get_default_config() + command = extension.get_command() + except Exception: + logger.exception( + "Setup of extension from entry point %s failed, " + "ignoring extension.", + entry_point.name, + ) + continue + + installed_extensions.append( + ExtensionData( + extension, entry_point, config_schema, default_config, command + ) + ) + + logger.debug( + "Loaded extension: %s %s", extension.dist_name, extension.version + ) + + names = (ed.extension.ext_name for ed in installed_extensions) + logger.debug("Discovered extensions: %s", ", ".join(names)) + return installed_extensions + + +def validate_extension_data(data): + """Verify extension's dependencies and environment. + + :param extensions: an extension to check + :returns: if extension should be run + """ + + logger.debug("Validating extension: %s", data.extension.ext_name) + + if data.extension.ext_name != data.entry_point.name: + logger.warning( + "Disabled extension %(ep)s: entry point name (%(ep)s) " + "does not match extension name (%(ext)s)", + {"ep": data.entry_point.name, "ext": data.extension.ext_name}, + ) + return False + + try: + data.entry_point.require() + except pkg_resources.DistributionNotFound as exc: + logger.info( + "Disabled extension %s: Dependency %s not found", + data.extension.ext_name, + exc, + ) + return False + except pkg_resources.VersionConflict as exc: + if len(exc.args) == 2: + found, required = exc.args + logger.info( + "Disabled extension %s: %s required, but found %s at %s", + data.extension.ext_name, + required, + found, + found.location, + ) + else: + logger.info( + "Disabled extension %s: %s", data.extension.ext_name, exc + ) + return False + + try: + data.extension.validate_environment() + except exceptions.ExtensionError as exc: + logger.info("Disabled extension %s: %s", data.extension.ext_name, exc) + return False + except Exception: + logger.exception( + "Validating extension %s failed with an exception.", + data.extension.ext_name, + ) + return False + + if not data.config_schema: + logger.error( + "Extension %s does not have a config schema, disabling.", + data.extension.ext_name, + ) + return False + elif not isinstance(data.config_schema.get("enabled"), config_lib.Boolean): + logger.error( + 'Extension %s does not have the required "enabled" config' + " option, disabling.", + data.extension.ext_name, + ) + return False + + for key, value in data.config_schema.items(): + if not isinstance(value, config_lib.ConfigValue): + logger.error( + "Extension %s config schema contains an invalid value" + ' for the option "%s", disabling.', + data.extension.ext_name, + key, + ) + return False + + if not data.config_defaults: + logger.error( + "Extension %s does not have a default config, disabling.", + data.extension.ext_name, + ) + return False + + return True diff --git a/venv/lib/python3.7/site-packages/mopidy/file/__init__.py b/venv/lib/python3.7/site-packages/mopidy/file/__init__.py new file mode 100644 index 0000000..bf5dbf9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/file/__init__.py @@ -0,0 +1,32 @@ +import logging +import os + +import mopidy +from mopidy import config, ext + +logger = logging.getLogger(__name__) + + +class Extension(ext.Extension): + + dist_name = "Mopidy-File" + ext_name = "file" + version = mopidy.__version__ + + def get_default_config(self): + conf_file = os.path.join(os.path.dirname(__file__), "ext.conf") + return config.read(conf_file) + + def get_config_schema(self): + schema = super().get_config_schema() + schema["media_dirs"] = config.List(optional=True) + schema["excluded_file_extensions"] = config.List(optional=True) + schema["show_dotfiles"] = config.Boolean(optional=True) + schema["follow_symlinks"] = config.Boolean(optional=True) + schema["metadata_timeout"] = config.Integer(optional=True) + return schema + + def setup(self, registry): + from .backend import FileBackend + + registry.add("backend", FileBackend) diff --git a/venv/lib/python3.7/site-packages/mopidy/file/backend.py b/venv/lib/python3.7/site-packages/mopidy/file/backend.py new file mode 100644 index 0000000..16cafde --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/file/backend.py @@ -0,0 +1,18 @@ +import logging + +import pykka + +from mopidy import backend +from mopidy.file import library + +logger = logging.getLogger(__name__) + + +class FileBackend(pykka.ThreadingActor, backend.Backend): + uri_schemes = ["file"] + + def __init__(self, config, audio): + super().__init__() + self.library = library.FileLibraryProvider(backend=self, config=config) + self.playback = backend.PlaybackProvider(audio=audio, backend=self) + self.playlists = None diff --git a/venv/lib/python3.7/site-packages/mopidy/file/ext.conf b/venv/lib/python3.7/site-packages/mopidy/file/ext.conf new file mode 100644 index 0000000..22b1f18 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/file/ext.conf @@ -0,0 +1,19 @@ +[file] +enabled = true +media_dirs = + $XDG_MUSIC_DIR|Music + ~/|Home +show_dotfiles = false +excluded_file_extensions = + .directory + .html + .jpeg + .jpg + .log + .nfo + .pdf + .png + .txt + .zip +follow_symlinks = false +metadata_timeout = 1000 diff --git a/venv/lib/python3.7/site-packages/mopidy/file/library.py b/venv/lib/python3.7/site-packages/mopidy/file/library.py new file mode 100644 index 0000000..58de047 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/file/library.py @@ -0,0 +1,148 @@ +import logging +import os + +from mopidy import backend, exceptions, models +from mopidy.audio import scan, tags +from mopidy.internal import path + +logger = logging.getLogger(__name__) + + +class FileLibraryProvider(backend.LibraryProvider): + """Library for browsing local files.""" + + # TODO: get_images that can pull from metadata and/or .folder.png etc? + # TODO: handle playlists? + + @property + def root_directory(self): + if not self._media_dirs: + return None + elif len(self._media_dirs) == 1: + uri = path.path_to_uri(self._media_dirs[0]["path"]) + else: + uri = "file:root" + return models.Ref.directory(name="Files", uri=uri) + + def __init__(self, backend, config): + super().__init__(backend) + self._media_dirs = list(self._get_media_dirs(config)) + self._show_dotfiles = config["file"]["show_dotfiles"] + self._excluded_file_extensions = tuple( + file_ext.lower() + for file_ext in config["file"]["excluded_file_extensions"] + ) + self._follow_symlinks = config["file"]["follow_symlinks"] + + self._scanner = scan.Scanner(timeout=config["file"]["metadata_timeout"]) + + def browse(self, uri): + logger.debug("Browsing files at: %s", uri) + result = [] + local_path = path.uri_to_path(uri) + + if str(local_path) == "root": + return list(self._get_media_dirs_refs()) + + if not self._is_in_basedir(local_path): + logger.warning( + "Rejected attempt to browse path (%s) outside dirs defined " + "in file/media_dirs config.", + uri, + ) + return [] + + for dir_entry in local_path.iterdir(): + child_path = dir_entry.resolve() + uri = path.path_to_uri(child_path) + + if not self._show_dotfiles and dir_entry.name.startswith("."): + continue + + if ( + self._excluded_file_extensions + and dir_entry.suffix in self._excluded_file_extensions + ): + continue + + if child_path.is_symlink() and not self._follow_symlinks: + logger.debug("Ignoring symlink: %s", uri) + continue + + if not self._is_in_basedir(child_path): + logger.debug("Ignoring symlink to outside base dir: %s", uri) + continue + + if child_path.is_dir(): + result.append( + models.Ref.directory(name=dir_entry.name, uri=uri) + ) + elif child_path.is_file(): + result.append(models.Ref.track(name=dir_entry.name, uri=uri)) + + def order(item): + return (item.type != models.Ref.DIRECTORY, item.name) + + result.sort(key=order) + + return result + + def lookup(self, uri): + logger.debug("Looking up file URI: %s", uri) + local_path = path.uri_to_path(uri) + + try: + result = self._scanner.scan(uri) + track = tags.convert_tags_to_track(result.tags).replace( + uri=uri, length=result.duration + ) + except exceptions.ScannerError as e: + logger.warning("Failed looking up %s: %s", uri, e) + track = models.Track(uri=uri) + + if not track.name: + track = track.replace(name=local_path.name) + + return [track] + + def _get_media_dirs(self, config): + for entry in config["file"]["media_dirs"]: + media_dir = {} + media_dir_split = entry.split("|", 1) + local_path = path.expand_path(media_dir_split[0]) + + if local_path is None: + logger.debug( + "Failed expanding path (%s) from file/media_dirs config " + "value.", + media_dir_split[0], + ) + continue + elif not local_path.is_dir(): + logger.warning( + "%s is not a directory. Please create the directory or " + "update the file/media_dirs config value.", + local_path, + ) + continue + + media_dir["path"] = local_path + if len(media_dir_split) == 2: + media_dir["name"] = media_dir_split[1] + else: + # TODO Mpd client should accept / in dir name + media_dir["name"] = media_dir_split[0].replace(os.sep, "+") + + yield media_dir + + def _get_media_dirs_refs(self): + for media_dir in self._media_dirs: + yield models.Ref.directory( + name=media_dir["name"], uri=path.path_to_uri(media_dir["path"]) + ) + + def _is_in_basedir(self, local_path): + return any( + path.is_path_inside_base_dir(local_path, media_dir["path"]) + for media_dir in self._media_dirs + ) diff --git a/venv/lib/python3.7/site-packages/mopidy/http/__init__.py b/venv/lib/python3.7/site-packages/mopidy/http/__init__.py new file mode 100644 index 0000000..7a9fa05 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/http/__init__.py @@ -0,0 +1,53 @@ +import logging +import os + +import mopidy +from mopidy import config as config_lib +from mopidy import exceptions, ext + +logger = logging.getLogger(__name__) + + +class Extension(ext.Extension): + dist_name = "Mopidy-HTTP" + ext_name = "http" + version = mopidy.__version__ + + def get_default_config(self): + conf_file = os.path.join(os.path.dirname(__file__), "ext.conf") + return config_lib.read(conf_file) + + def get_config_schema(self): + schema = super().get_config_schema() + schema["hostname"] = config_lib.Hostname() + schema["port"] = config_lib.Port() + schema["static_dir"] = config_lib.Deprecated() + schema["zeroconf"] = config_lib.String(optional=True) + schema["allowed_origins"] = config_lib.List(optional=True) + schema["csrf_protection"] = config_lib.Boolean(optional=True) + schema["default_app"] = config_lib.String(optional=True) + return schema + + def validate_environment(self): + try: + import tornado.web # noqa + except ImportError as e: + raise exceptions.ExtensionError("tornado library not found", e) + + def setup(self, registry): + from .actor import HttpFrontend + from .handlers import make_mopidy_app_factory + + HttpFrontend.apps = registry["http:app"] + HttpFrontend.statics = registry["http:static"] + + registry.add("frontend", HttpFrontend) + registry.add( + "http:app", + { + "name": "mopidy", + "factory": make_mopidy_app_factory( + registry["http:app"], registry["http:static"] + ), + }, + ) diff --git a/venv/lib/python3.7/site-packages/mopidy/http/actor.py b/venv/lib/python3.7/site-packages/mopidy/http/actor.py new file mode 100644 index 0000000..603b5d6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/http/actor.py @@ -0,0 +1,210 @@ +import json +import logging +import secrets +import threading + +import pykka +import tornado.httpserver +import tornado.ioloop +import tornado.netutil +import tornado.web +import tornado.websocket + +from mopidy import exceptions, models, zeroconf +from mopidy.core import CoreListener +from mopidy.http import Extension, handlers +from mopidy.internal import formatting, network + +try: + import asyncio +except ImportError: + asyncio = None + + +logger = logging.getLogger(__name__) + + +class HttpFrontend(pykka.ThreadingActor, CoreListener): + apps = [] + statics = [] + + def __init__(self, config, core): + super().__init__() + + self.hostname = network.format_hostname(config["http"]["hostname"]) + self.port = config["http"]["port"] + tornado_hostname = config["http"]["hostname"] + if tornado_hostname == "::": + tornado_hostname = None + + try: + logger.debug("Starting HTTP server") + sockets = tornado.netutil.bind_sockets(self.port, tornado_hostname) + self.server = HttpServer( + config=config, + core=core, + sockets=sockets, + apps=self.apps, + statics=self.statics, + ) + except OSError as exc: + raise exceptions.FrontendError(f"HTTP server startup failed: {exc}") + + self.zeroconf_name = config["http"]["zeroconf"] + self.zeroconf_http = None + self.zeroconf_mopidy_http = None + + def on_start(self): + logger.info("HTTP server running at [%s]:%s", self.hostname, self.port) + self.server.start() + + if self.zeroconf_name: + self.zeroconf_http = zeroconf.Zeroconf( + name=self.zeroconf_name, stype="_http._tcp", port=self.port + ) + self.zeroconf_mopidy_http = zeroconf.Zeroconf( + name=self.zeroconf_name, + stype="_mopidy-http._tcp", + port=self.port, + ) + self.zeroconf_http.publish() + self.zeroconf_mopidy_http.publish() + + def on_stop(self): + if self.zeroconf_http: + self.zeroconf_http.unpublish() + if self.zeroconf_mopidy_http: + self.zeroconf_mopidy_http.unpublish() + + self.server.stop() + + def on_event(self, name, **data): + on_event(name, self.server.io_loop, **data) + + +def on_event(name, io_loop, **data): + event = data + event["event"] = name + message = json.dumps(event, cls=models.ModelJSONEncoder) + handlers.WebSocketHandler.broadcast(message, io_loop) + + +class HttpServer(threading.Thread): + name = "HttpServer" + + def __init__(self, config, core, sockets, apps, statics): + super().__init__() + + self.config = config + self.core = core + self.sockets = sockets + self.apps = apps + self.statics = statics + + self.app = None + self.server = None + self.io_loop = None + + def run(self): + if asyncio: + # If asyncio is available, Tornado uses it as its IO loop. Since we + # start Tornado in a another thread than the main thread, we must + # explicitly create an asyncio loop for the current thread. + asyncio.set_event_loop(asyncio.new_event_loop()) + + self.app = tornado.web.Application( + self._get_request_handlers(), + cookie_secret=self._get_cookie_secret(), + ) + self.server = tornado.httpserver.HTTPServer(self.app) + self.server.add_sockets(self.sockets) + + self.io_loop = tornado.ioloop.IOLoop.current() + self.io_loop.start() + + logger.debug("Stopped HTTP server") + + def stop(self): + logger.debug("Stopping HTTP server") + self.io_loop.add_callback(self.io_loop.stop) + + def _get_request_handlers(self): + request_handlers = [] + request_handlers.extend(self._get_app_request_handlers()) + request_handlers.extend(self._get_static_request_handlers()) + request_handlers.extend(self._get_default_request_handlers()) + + logger.debug( + "HTTP routes from extensions: %s", + formatting.indent( + "\n".join( + f"{path!r}: {handler!r}" + for (path, handler, *_) in request_handlers + ) + ), + ) + + return request_handlers + + def _get_app_request_handlers(self): + result = [] + for app in self.apps: + try: + request_handlers = app["factory"](self.config, self.core) + except Exception: + logger.exception("Loading %s failed.", app["name"]) + continue + + result.append((f"/{app['name']}", handlers.AddSlashHandler)) + for handler in request_handlers: + handler = list(handler) + handler[0] = f"/{app['name']}{handler[0]}" + result.append(tuple(handler)) + logger.debug("Loaded HTTP extension: %s", app["name"]) + return result + + def _get_static_request_handlers(self): + result = [] + for static in self.statics: + result.append((f"/{static['name']}", handlers.AddSlashHandler)) + result.append( + ( + f"/{static['name']}/(.*)", + handlers.StaticFileHandler, + {"path": static["path"], "default_filename": "index.html"}, + ) + ) + logger.debug("Loaded static HTTP extension: %s", static["name"]) + return result + + def _get_default_request_handlers(self): + sites = [app["name"] for app in self.apps + self.statics] + + default_app = self.config["http"]["default_app"] + if default_app not in sites: + logger.warning( + f"HTTP server's default app {default_app!r} not found" + ) + default_app = "mopidy" + logger.debug(f"Default webclient is {default_app}") + + return [ + ( + r"/", + tornado.web.RedirectHandler, + {"url": f"/{default_app}/", "permanent": False}, + ) + ] + + def _get_cookie_secret(self): + file_path = Extension.get_data_dir(self.config) / "cookie_secret" + if not file_path.is_file(): + cookie_secret = secrets.token_hex(32) + file_path.write_text(cookie_secret) + else: + cookie_secret = file_path.read_text().strip() + if not cookie_secret: + logging.error( + f"HTTP server could not find cookie secret in {file_path}" + ) + return cookie_secret diff --git a/venv/lib/python3.7/site-packages/mopidy/http/data/clients.html b/venv/lib/python3.7/site-packages/mopidy/http/data/clients.html new file mode 100644 index 0000000..feff4fe --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/http/data/clients.html @@ -0,0 +1,31 @@ + + + + + + Mopidy + + + +
+

Mopidy

+ +

This web server is a part of the Mopidy music server. To learn more + about Mopidy, please visit + www.mopidy.com.

+
+ +
+

Web clients

+ + + +

Web clients which are installed as Mopidy extensions will + automatically appear here.

+
+ + diff --git a/venv/lib/python3.7/site-packages/mopidy/http/data/favicon.ico b/venv/lib/python3.7/site-packages/mopidy/http/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a214c91fb4fa045dfbb5ea7d5414eda99d6d4dbe GIT binary patch literal 5997 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.7/site-packages/mopidy/http/data/mopidy.css b/venv/lib/python3.7/site-packages/mopidy/http/data/mopidy.css new file mode 100644 index 0000000..0bf4522 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/http/data/mopidy.css @@ -0,0 +1,43 @@ +html { + background: #f8f8f8; + color: #555; + font-family: Geneva, Tahoma, Verdana, sans-serif; + line-height: 1.4em; +} +body { + max-width: 600px; + margin: 0 auto; +} +h1, h2 { + font-weight: 500; + line-height: 1.1em; +} +a { + color: #555; + text-decoration: none; + border-bottom: 1px dotted; +} +img { + border: 0; +} + +.box { + background: white; + box-shadow: 0px 5px 5px #f0f0f0; + margin: 1em; + padding: 1em; +} +.box.focus { + background: #465158; + color: #e8ecef; +} + +.box a { + color: #465158; +} +.box a:hover { + opacity: 0.8; +} +.box.focus a { + color: #e8ecef; +} diff --git a/venv/lib/python3.7/site-packages/mopidy/http/ext.conf b/venv/lib/python3.7/site-packages/mopidy/http/ext.conf new file mode 100644 index 0000000..c986fc3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/http/ext.conf @@ -0,0 +1,8 @@ +[http] +enabled = true +hostname = 127.0.0.1 +port = 6680 +zeroconf = Mopidy HTTP server on $hostname +allowed_origins = +csrf_protection = true +default_app = mopidy diff --git a/venv/lib/python3.7/site-packages/mopidy/http/handlers.py b/venv/lib/python3.7/site-packages/mopidy/http/handlers.py new file mode 100644 index 0000000..130ced3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/http/handlers.py @@ -0,0 +1,273 @@ +import functools +import logging +import os +import urllib + +import tornado.escape +import tornado.ioloop +import tornado.web +import tornado.websocket + +import mopidy +from mopidy import core, models +from mopidy.internal import jsonrpc + +logger = logging.getLogger(__name__) + + +def make_mopidy_app_factory(apps, statics): + def mopidy_app_factory(config, core): + if not config["http"]["csrf_protection"]: + logger.warning( + "HTTP Cross-Site Request Forgery protection is disabled" + ) + allowed_origins = { + x.lower() for x in config["http"]["allowed_origins"] if x + } + return [ + ( + r"/ws/?", + WebSocketHandler, + { + "core": core, + "allowed_origins": allowed_origins, + "csrf_protection": config["http"]["csrf_protection"], + }, + ), + ( + r"/rpc", + JsonRpcHandler, + { + "core": core, + "allowed_origins": allowed_origins, + "csrf_protection": config["http"]["csrf_protection"], + }, + ), + ( + r"/(.+)", + StaticFileHandler, + {"path": os.path.join(os.path.dirname(__file__), "data")}, + ), + (r"/", ClientListHandler, {"apps": apps, "statics": statics}), + ] + + return mopidy_app_factory + + +def make_jsonrpc_wrapper(core_actor): + inspector = jsonrpc.JsonRpcInspector( + objects={ + "core.get_uri_schemes": core.Core.get_uri_schemes, + "core.get_version": core.Core.get_version, + "core.history": core.HistoryController, + "core.library": core.LibraryController, + "core.mixer": core.MixerController, + "core.playback": core.PlaybackController, + "core.playlists": core.PlaylistsController, + "core.tracklist": core.TracklistController, + } + ) + return jsonrpc.JsonRpcWrapper( + objects={ + "core.describe": inspector.describe, + "core.get_uri_schemes": core_actor.get_uri_schemes, + "core.get_version": core_actor.get_version, + "core.history": core_actor.history, + "core.library": core_actor.library, + "core.mixer": core_actor.mixer, + "core.playback": core_actor.playback, + "core.playlists": core_actor.playlists, + "core.tracklist": core_actor.tracklist, + }, + decoders=[models.model_json_decoder], + encoders=[models.ModelJSONEncoder], + ) + + +def _send_broadcast(client, msg): + # We could check for client.ws_connection, but we don't really + # care why the broadcast failed, we just want the rest of them + # to succeed, so catch everything. + try: + client.write_message(msg) + except Exception as exc: + logger.debug( + f"Broadcast of WebSocket message to " + f"{client.request.remote_ip} failed: {exc}" + ) + # TODO: should this do the same cleanup as the on_message code? + + +class WebSocketHandler(tornado.websocket.WebSocketHandler): + + # XXX This set is shared by all WebSocketHandler objects. This isn't + # optimal, but there's currently no use case for having more than one of + # these anyway. + clients = set() + + @classmethod + def broadcast(cls, msg, io_loop): + # This can be called from outside the Tornado ioloop, so we need to + # safely cross the thread boundary by adding a callback to the loop. + for client in cls.clients: + # One callback per client to keep time we hold up the loop short + io_loop.add_callback( + functools.partial(_send_broadcast, client, msg) + ) + + def initialize(self, core, allowed_origins, csrf_protection): + self.jsonrpc = make_jsonrpc_wrapper(core) + self.allowed_origins = allowed_origins + self.csrf_protection = csrf_protection + + def open(self): + self.set_nodelay(True) + self.clients.add(self) + logger.debug("New WebSocket connection from %s", self.request.remote_ip) + + def on_close(self): + self.clients.discard(self) + logger.debug( + "Closed WebSocket connection from %s", self.request.remote_ip + ) + + def on_message(self, message): + if not message: + return + + logger.debug( + "Received WebSocket message from %s: %r", + self.request.remote_ip, + message, + ) + + try: + response = self.jsonrpc.handle_json( + tornado.escape.native_str(message) + ) + if response and self.write_message(response): + logger.debug( + "Sent WebSocket message to %s: %r", + self.request.remote_ip, + response, + ) + except Exception as exc: + logger.error(f"WebSocket request error: {exc}") + self.close() + + def check_origin(self, origin): + if not self.csrf_protection: + return True + return check_origin(origin, self.request.headers, self.allowed_origins) + + +def set_mopidy_headers(request_handler): + request_handler.set_header("Cache-Control", "no-cache") + request_handler.set_header("X-Mopidy-Version", mopidy.__version__.encode()) + + +def check_origin(origin, request_headers, allowed_origins): + if origin is None: + logger.warning("HTTP request denied for missing Origin header") + return False + allowed_origins.add(request_headers.get("Host")) + parsed_origin = urllib.parse.urlparse(origin).netloc.lower() + # Some frameworks (e.g. Apache Cordova) use local files. Requests from + # these files don't really have a sensible Origin so the browser sets the + # header to something like 'file://' or 'null'. This results here in an + # empty parsed_origin which we choose to allow. + if parsed_origin and parsed_origin not in allowed_origins: + logger.warning('HTTP request denied for Origin "%s"', origin) + return False + return True + + +class JsonRpcHandler(tornado.web.RequestHandler): + def initialize(self, core, allowed_origins, csrf_protection): + self.jsonrpc = make_jsonrpc_wrapper(core) + self.allowed_origins = allowed_origins + self.csrf_protection = csrf_protection + + def head(self): + self.set_extra_headers() + self.finish() + + def post(self): + if self.csrf_protection: + content_type = self.request.headers.get("Content-Type", "") + if content_type != "application/json": + self.set_status(415, "Content-Type must be application/json") + return + + data = self.request.body + if not data: + return + + logger.debug( + "Received RPC message from %s: %r", self.request.remote_ip, data + ) + + try: + self.set_extra_headers() + response = self.jsonrpc.handle_json(tornado.escape.native_str(data)) + if response and self.write(response): + logger.debug( + "Sent RPC message to %s: %r", + self.request.remote_ip, + response, + ) + except Exception as e: + logger.error("HTTP JSON-RPC request error: %s", e) + self.write_error(500) + + def set_extra_headers(self): + set_mopidy_headers(self) + self.set_header("Accept", "application/json") + self.set_header("Content-Type", "application/json; utf-8") + + def options(self): + if self.csrf_protection: + origin = self.request.headers.get("Origin") + if not check_origin( + origin, self.request.headers, self.allowed_origins + ): + self.set_status(403, f"Access denied for origin {origin}") + return + + self.set_header("Access-Control-Allow-Origin", f"{origin}") + self.set_header("Access-Control-Allow-Headers", "Content-Type") + + self.set_status(204) + self.finish() + + +class ClientListHandler(tornado.web.RequestHandler): + def initialize(self, apps, statics): + self.apps = apps + self.statics = statics + + def get_template_path(self): + return os.path.dirname(__file__) + + def get(self): + set_mopidy_headers(self) + + names = set() + for app in self.apps: + names.add(app["name"]) + for static in self.statics: + names.add(static["name"]) + names.discard("mopidy") + + self.render("data/clients.html", apps=sorted(list(names))) + + +class StaticFileHandler(tornado.web.StaticFileHandler): + def set_extra_headers(self, path): + set_mopidy_headers(self) + + +class AddSlashHandler(tornado.web.RequestHandler): + @tornado.web.addslash + def prepare(self): + return super().prepare() diff --git a/venv/lib/python3.7/site-packages/mopidy/httpclient.py b/venv/lib/python3.7/site-packages/mopidy/httpclient.py new file mode 100644 index 0000000..b3316dd --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/httpclient.py @@ -0,0 +1,50 @@ +import platform + +import mopidy + +"Helpers for configuring HTTP clients used in Mopidy extensions." + + +def format_proxy(proxy_config, auth=True): + """Convert a Mopidy proxy config to the commonly used proxy string format. + + Outputs ``scheme://host:port``, ``scheme://user:pass@host:port`` or + :class:`None` depending on the proxy config provided. + + You can also opt out of getting the basic auth by setting ``auth`` to + :class:`False`. + + .. versionadded:: 1.1 + """ + if not proxy_config.get("hostname"): + return None + + scheme = proxy_config.get("scheme") or "http" + username = proxy_config.get("username") + password = proxy_config.get("password") + hostname = proxy_config["hostname"] + port = proxy_config.get("port") + if not port or port < 0: + port = 80 + + if username and password and auth: + return f"{scheme}://{username}:{password}@{hostname}:{port}" + else: + return f"{scheme}://{hostname}:{port}" + + +def format_user_agent(name=None): + """Construct a User-Agent suitable for use in client code. + + This will identify use by the provided ``name`` (which should be on the + format ``dist_name/version``), Mopidy version and Python version. + + .. versionadded:: 1.1 + """ + parts = [ + f"Mopidy/{mopidy.__version__}", + f"{platform.python_implementation()}/{platform.python_version()}", + ] + if name: + parts.insert(0, name) + return " ".join(parts) diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/__init__.py b/venv/lib/python3.7/site-packages/mopidy/internal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/deprecation.py b/venv/lib/python3.7/site-packages/mopidy/internal/deprecation.py new file mode 100644 index 0000000..3d391cb --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/deprecation.py @@ -0,0 +1,52 @@ +import contextlib +import re +import warnings + +# Messages used in deprecation warnings are collected here so we can target +# them easily when ignoring warnings. +_MESSAGES = { + # Deprecated features in core playback: + "core.playback.play:tl_track_kwargs": ( + 'playback.play() with "tl_track" argument is pending deprecation use ' + '"tlid" instead' + ), + # Deprecated features in core tracklist: + "core.tracklist.add:tracks_arg": ( + 'tracklist.add() "tracks" argument is deprecated' + ), + "core.tracklist.eot_track": ( + "tracklist.eot_track() is pending deprecation, use " + "tracklist.get_eot_tlid()" + ), + "core.tracklist.next_track": ( + "tracklist.next_track() is pending deprecation, use " + "tracklist.get_next_tlid()" + ), + "core.tracklist.previous_track": ( + "tracklist.previous_track() is pending deprecation, use " + "tracklist.get_previous_tlid()" + ), +} + + +def warn(msg_id, pending=False): + if pending: + category = PendingDeprecationWarning + else: + category = DeprecationWarning + warnings.warn(_MESSAGES.get(msg_id, msg_id), category) + + +@contextlib.contextmanager +def ignore(ids=None): + with warnings.catch_warnings(): + if isinstance(ids, str): + ids = [ids] + + if ids: + for msg_id in ids: + msg = re.escape(_MESSAGES.get(msg_id, msg_id)) + warnings.filterwarnings("ignore", msg, DeprecationWarning) + else: + warnings.filterwarnings("ignore", category=DeprecationWarning) + yield diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/deps.py b/venv/lib/python3.7/site-packages/mopidy/internal/deps.py new file mode 100644 index 0000000..d1a51c1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/deps.py @@ -0,0 +1,189 @@ +import functools +import os +import platform +import sys + +import pkg_resources + +from mopidy.internal import formatting +from mopidy.internal.gi import Gst, gi + + +def format_dependency_list(adapters=None): + if adapters is None: + dist_names = { + ep.dist.project_name + for ep in pkg_resources.iter_entry_points("mopidy.ext") + if ep.dist.project_name != "Mopidy" + } + dist_infos = [ + functools.partial(pkg_info, dist_name) for dist_name in dist_names + ] + + adapters = ( + [ + executable_info, + platform_info, + python_info, + functools.partial(pkg_info, "Mopidy", True), + ] + + dist_infos + + [gstreamer_info] + ) + + return "\n".join([_format_dependency(a()) for a in adapters]) + + +def _format_dependency(dep_info): + lines = [] + + if "version" not in dep_info: + lines.append(f"{dep_info['name']}: not found") + else: + source = f" from {dep_info['path']}" if "path" in dep_info else "" + lines.append(f"{dep_info['name']}: {dep_info['version']}{source}") + + if "other" in dep_info: + details = formatting.indent(dep_info["other"], places=4) + lines.append(f" Detailed information: {details}") + + if dep_info.get("dependencies", []): + for sub_dep_info in dep_info["dependencies"]: + sub_dep_lines = _format_dependency(sub_dep_info) + lines.append( + formatting.indent(sub_dep_lines, places=2, singles=True) + ) + + return "\n".join(lines) + + +def executable_info(): + return { + "name": "Executable", + "version": sys.argv[0], + } + + +def platform_info(): + return { + "name": "Platform", + "version": platform.platform(), + } + + +def python_info(): + return { + "name": "Python", + "version": ( + f"{platform.python_implementation()} {platform.python_version()}" + ), + "path": os.path.dirname(platform.__file__), + } + + +def pkg_info( + project_name=None, include_transitive_deps=True, include_extras=False +): + if project_name is None: + project_name = "Mopidy" + try: + distribution = pkg_resources.get_distribution(project_name) + extras = include_extras and distribution.extras or [] + if include_transitive_deps: + dependencies = [ + pkg_info( + d.project_name, + include_transitive_deps=d.project_name != "Mopidy", + ) + for d in distribution.requires(extras) + ] + else: + dependencies = [] + return { + "name": project_name, + "version": distribution.version, + "path": distribution.location, + "dependencies": dependencies, + } + except pkg_resources.ResolutionError: + return { + "name": project_name, + } + + +def gstreamer_info(): + other = [] + other.append(f"Python wrapper: python-gi {gi.__version__}") + + found_elements = [] + missing_elements = [] + for name, status in _gstreamer_check_elements(): + if status: + found_elements.append(name) + else: + missing_elements.append(name) + + other.append("Relevant elements:") + other.append(" Found:") + for element in found_elements: + other.append(f" {element}") + if not found_elements: + other.append(" none") + other.append(" Not found:") + for element in missing_elements: + other.append(f" {element}") + if not missing_elements: + other.append(" none") + + return { + "name": "GStreamer", + "version": ".".join(map(str, Gst.version())), + "path": os.path.dirname(gi.__file__), + "other": "\n".join(other), + } + + +def _gstreamer_check_elements(): + elements_to_check = [ + # Core playback + "uridecodebin", + # External HTTP streams + "souphttpsrc", + # Spotify + "appsrc", + # Audio sinks + "alsasink", + "osssink", + "oss4sink", + "pulsesink", + # MP3 encoding and decoding + # + # One of flump3dec, mad, and mpg123audiodec is required for MP3 + # playback. + "flump3dec", + "id3demux", + "id3v2mux", + "lamemp3enc", + "mad", + "mpegaudioparse", + "mpg123audiodec", + # Ogg Vorbis encoding and decoding + "vorbisdec", + "vorbisenc", + "vorbisparse", + "oggdemux", + "oggmux", + "oggparse", + # Flac decoding + "flacdec", + "flacparse", + # Shoutcast output + "shout2send", + ] + known_elements = [ + factory.get_name() + for factory in Gst.Registry.get().get_feature_list(Gst.ElementFactory) + ] + return [ + (element, element in known_elements) for element in elements_to_check + ] diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/formatting.py b/venv/lib/python3.7/site-packages/mopidy/internal/formatting.py new file mode 100644 index 0000000..58220ad --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/formatting.py @@ -0,0 +1,28 @@ +import re +import unicodedata + + +def indent(string, places=4, linebreak="\n", singles=False): + lines = string.split(linebreak) + if not singles and len(lines) == 1: + return string + for i, line in enumerate(lines): + lines[i] = " " * places + line + result = linebreak.join(lines) + if not singles: + result = linebreak + result + return result + + +def slugify(value): + """ + Converts to lowercase, removes non-word characters (alphanumerics and + underscores) and converts spaces to hyphens. Also strips leading and + trailing whitespace. + + This function is based on Django's slugify implementation. + """ + value = unicodedata.normalize("NFKD", value) + value = value.encode("ascii", "ignore").decode("ascii") + value = re.sub(r"[^\w\s-]", "", value).strip().lower() + return re.sub(r"[-\s]+", "-", value) diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/gi.py b/venv/lib/python3.7/site-packages/mopidy/internal/gi.py new file mode 100644 index 0000000..031ba8f --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/gi.py @@ -0,0 +1,49 @@ +import sys +import textwrap + +try: + import gi + + gi.require_version("Gst", "1.0") + from gi.repository import GLib, GObject, Gst +except ImportError: + print( + textwrap.dedent( + """ + ERROR: A GObject based library was not found. + + Mopidy requires GStreamer to work. GStreamer is a C library with a + number of dependencies itself, and cannot be installed with the regular + Python tools like pip. + + Please see http://docs.mopidy.com/en/latest/installation/ for + instructions on how to install the required dependencies. + """ + ) + ) + raise +else: + Gst.init([]) + gi.require_version("GstPbutils", "1.0") + from gi.repository import GstPbutils + +GLib.set_prgname("mopidy") +GLib.set_application_name("Mopidy") + +REQUIRED_GST_VERSION = (1, 14, 0) +REQUIRED_GST_VERSION_DISPLAY = ".".join(map(str, REQUIRED_GST_VERSION)) + +if Gst.version() < REQUIRED_GST_VERSION: + sys.exit( + f"ERROR: Mopidy requires GStreamer >= {REQUIRED_GST_VERSION_DISPLAY}, " + f"but found {Gst.version_string()}." + ) + + +__all__ = [ + "GLib", + "GObject", + "Gst", + "GstPbutils", + "gi", +] diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/http.py b/venv/lib/python3.7/site-packages/mopidy/internal/http.py new file mode 100644 index 0000000..2cedcbe --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/http.py @@ -0,0 +1,57 @@ +import logging +import time + +import requests + +from mopidy import httpclient + +logger = logging.getLogger(__name__) + + +def get_requests_session(proxy_config, user_agent): + proxy = httpclient.format_proxy(proxy_config) + full_user_agent = httpclient.format_user_agent(user_agent) + + session = requests.Session() + session.proxies.update({"http": proxy, "https": proxy}) + session.headers.update({"user-agent": full_user_agent}) + + return session + + +def download(session, uri, timeout=1.0, chunk_size=4096): + try: + response = session.get(uri, stream=True, timeout=timeout) + except requests.exceptions.Timeout: + logger.warning( + "Download of %r failed due to connection timeout after " "%.3fs", + uri, + timeout, + ) + return None + except requests.exceptions.InvalidSchema: + logger.warning("Download of %r failed due to unsupported schema", uri) + return None + except requests.exceptions.RequestException as exc: + logger.warning("Download of %r failed: %s", uri, exc) + logger.debug("Download exception details", exc_info=True) + return None + + content = [] + deadline = time.time() + timeout + for chunk in response.iter_content(chunk_size): + content.append(chunk) + if time.time() > deadline: + logger.warning( + "Download of %r failed due to download taking more " + "than %.3fs", + uri, + timeout, + ) + return None + + if not response.ok: + logger.warning("Problem downloading %r: %s", uri, response.reason) + return None + + return b"".join(content) diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/jsonrpc.py b/venv/lib/python3.7/site-packages/mopidy/internal/jsonrpc.py new file mode 100644 index 0000000..c20162a --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/jsonrpc.py @@ -0,0 +1,387 @@ +import inspect +import json +import traceback + +import pykka + + +class JsonRpcWrapper: + + """ + Wrap objects and make them accessible through JSON-RPC 2.0 messaging. + + This class takes responsibility of communicating with the objects and + processing of JSON-RPC 2.0 messages. The transport of the messages over + HTTP, WebSocket, TCP, or whatever is of no concern to this class. + + The wrapper supports exporting the methods of one or more objects. Either + way, the objects must be exported with method name prefixes, called + "mounts". + + To expose objects, add them all to the objects mapping. The key in the + mapping is used as the object's mounting point in the exposed API:: + + jrw = JsonRpcWrapper(objects={ + 'foo': foo, + 'hello': lambda: 'Hello, world!', + }) + + This will export the Python callables on the left as the JSON-RPC 2.0 + method names on the right:: + + foo.bar() -> foo.bar + foo.baz() -> foo.baz + lambda -> hello + + Only the public methods of the mounted objects, or functions/methods + included directly in the mapping, will be exposed. + + If a method returns a :class:`pykka.Future`, the future will be completed + and its value unwrapped before the JSON-RPC wrapper returns the response. + + For further details on the JSON-RPC 2.0 spec, see + http://www.jsonrpc.org/specification + + :param objects: mapping between mounting points and exposed functions or + class instances + :type objects: dict + :param decoders: object builders to be used by :func`json.loads` + :type decoders: list of functions taking a dict and returning a dict + :param encoders: object serializers to be used by :func:`json.dumps` + :type encoders: list of :class:`json.JSONEncoder` subclasses with the + method :meth:`default` implemented + """ + + def __init__(self, objects, decoders=None, encoders=None): + if "" in objects.keys(): + raise AttributeError( + "The empty string is not allowed as an object mount" + ) + self.objects = objects + self.decoder = get_combined_json_decoder(decoders or []) + self.encoder = get_combined_json_encoder(encoders or []) + + def handle_json(self, request): + """ + Handles an incoming request encoded as a JSON string. + + Returns a response as a JSON string for commands, and :class:`None` for + notifications. + + :param request: the serialized JSON-RPC request + :type request: string + :rtype: string or :class:`None` + """ + try: + request = json.loads(request, object_hook=self.decoder) + except ValueError: + response = JsonRpcParseError().get_response() + else: + response = self.handle_data(request) + if response is None: + return None + return json.dumps(response, cls=self.encoder) + + def handle_data(self, request): + """ + Handles an incoming request in the form of a Python data structure. + + Returns a Python data structure for commands, or a :class:`None` for + notifications. + + :param request: the unserialized JSON-RPC request + :type request: dict + :rtype: dict, list, or :class:`None` + """ + if isinstance(request, list): + return self._handle_batch(request) + else: + return self._handle_single_request(request) + + def _handle_batch(self, requests): + if not requests: + return JsonRpcInvalidRequestError( + data="Batch list cannot be empty" + ).get_response() + + responses = [] + for request in requests: + response = self._handle_single_request(request) + if response: + responses.append(response) + + return responses or None + + def _handle_single_request(self, request): + try: + self._validate_request(request) + args, kwargs = self._get_params(request) + except JsonRpcInvalidRequestError as error: + return error.get_response() + + try: + method = self._get_method(request["method"]) + + try: + result = method(*args, **kwargs) + + if self._is_notification(request): + return None + + result = self._unwrap_result(result) + + return { + "jsonrpc": "2.0", + "id": request["id"], + "result": result, + } + except TypeError as error: + raise JsonRpcInvalidParamsError( + data={ + "type": error.__class__.__name__, + "message": str(error), + "traceback": traceback.format_exc(), + } + ) + except Exception as error: + raise JsonRpcApplicationError( + data={ + "type": error.__class__.__name__, + "message": str(error), + "traceback": traceback.format_exc(), + } + ) + except JsonRpcError as error: + if self._is_notification(request): + return None + return error.get_response(request["id"]) + + def _validate_request(self, request): + if not isinstance(request, dict): + raise JsonRpcInvalidRequestError(data="Request must be an object") + if "jsonrpc" not in request: + raise JsonRpcInvalidRequestError( + data="'jsonrpc' member must be included" + ) + if request["jsonrpc"] != "2.0": + raise JsonRpcInvalidRequestError( + data="'jsonrpc' value must be '2.0'" + ) + if "method" not in request: + raise JsonRpcInvalidRequestError( + data="'method' member must be included" + ) + if not isinstance(request["method"], str): + raise JsonRpcInvalidRequestError(data="'method' must be a string") + + def _get_params(self, request): + if "params" not in request: + return [], {} + params = request["params"] + if isinstance(params, list): + return params, {} + elif isinstance(params, dict): + return [], params + else: + raise JsonRpcInvalidRequestError( + data="'params', if given, must be an array or an object" + ) + + def _get_method(self, method_path): + if callable(self.objects.get(method_path, None)): + # The mounted object is the callable + return self.objects[method_path] + + # The mounted object contains the callable + + if "." not in method_path: + raise JsonRpcMethodNotFoundError( + data=f"Could not find object mount in method name {method_path!r}" + ) + + mount, method_name = method_path.rsplit(".", 1) + + if method_name.startswith("_"): + raise JsonRpcMethodNotFoundError( + data="Private methods are not exported" + ) + + try: + obj = self.objects[mount] + except KeyError: + raise JsonRpcMethodNotFoundError( + data=f"No object found at {mount!r}" + ) + + try: + return getattr(obj, method_name) + except AttributeError: + raise JsonRpcMethodNotFoundError( + data=f"Object mounted at {mount!r} has no member {method_name!r}" + ) + + def _is_notification(self, request): + return "id" not in request + + def _unwrap_result(self, result): + if isinstance(result, pykka.Future): + result = result.get() + return result + + +class JsonRpcError(Exception): + code = -32000 + message = "Unspecified server error" + + def __init__(self, data=None): + self.data = data + + def get_response(self, request_id=None): + response = { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": self.code, "message": self.message}, + } + if self.data: + response["error"]["data"] = self.data + return response + + +class JsonRpcParseError(JsonRpcError): + code = -32700 + message = "Parse error" + + +class JsonRpcInvalidRequestError(JsonRpcError): + code = -32600 + message = "Invalid Request" + + +class JsonRpcMethodNotFoundError(JsonRpcError): + code = -32601 + message = "Method not found" + + +class JsonRpcInvalidParamsError(JsonRpcError): + code = -32602 + message = "Invalid params" + + +class JsonRpcApplicationError(JsonRpcError): + code = 0 + message = "Application error" + + +def get_combined_json_decoder(decoders): + def decode(dct): + for decoder in decoders: + dct = decoder(dct) + return dct + + return decode + + +def get_combined_json_encoder(encoders): + class JsonRpcEncoder(json.JSONEncoder): + def default(self, obj): + for encoder in encoders: + try: + return encoder().default(obj) + except TypeError: + pass # Try next encoder + return json.JSONEncoder.default(self, obj) + + return JsonRpcEncoder + + +class JsonRpcInspector: + + """ + Inspects a group of classes and functions to create a description of what + methods they can expose over JSON-RPC 2.0. + + To inspect one or more classes, add them all to the objects mapping. The + key in the mapping is used as the classes' mounting point in the exposed + API:: + + jri = JsonRpcInspector(objects={ + 'foo': Foo, + 'hello': lambda: 'Hello, world!', + }) + + Since the inspector is based on inspecting classes and not instances, it + will not include methods added dynamically. The wrapper works with + instances, and it will thus export dynamically added methods as well. + + :param objects: mapping between mounts and exposed functions or classes + :type objects: dict + """ + + def __init__(self, objects): + if "" in objects.keys(): + raise AttributeError( + "The empty string is not allowed as an object mount" + ) + self.objects = objects + + def describe(self): + """ + Inspects the object and returns a data structure which describes the + available properties and methods. + """ + methods = {} + for mount, obj in self.objects.items(): + if inspect.isroutine(obj): + methods[mount] = self._describe_method(obj) + else: + obj_methods = self._get_methods(obj) + for name, description in obj_methods.items(): + if mount: + name = f"{mount}.{name}" + methods[name] = description + return methods + + def _get_methods(self, obj): + methods = {} + for name, value in inspect.getmembers(obj): + if name.startswith("_"): + continue + if not inspect.isroutine(value): + continue + method = self._describe_method(value) + if method: + methods[name] = method + return methods + + def _describe_method(self, method): + return { + "description": inspect.getdoc(method), + "params": self._describe_params(method), + } + + def _describe_params(self, method): + argspec = inspect.getfullargspec(method) + + defaults = argspec.defaults and list(argspec.defaults) or [] + num_args_without_default = len(argspec.args) - len(defaults) + no_defaults = [None] * num_args_without_default + defaults = no_defaults + defaults + + params = [] + + for arg, _default in zip(argspec.args, defaults): + if arg == "self": + continue + params.append({"name": arg}) + + if argspec.defaults: + for i, default in enumerate(reversed(argspec.defaults)): + params[len(params) - i - 1]["default"] = default + + if argspec.varargs: + params.append({"name": argspec.varargs, "varargs": True}) + + if argspec.varkw: + params.append({"name": argspec.varkw, "kwargs": True}) + + return params diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/log.py b/venv/lib/python3.7/site-packages/mopidy/internal/log.py new file mode 100644 index 0000000..d084ef4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/log.py @@ -0,0 +1,199 @@ +import logging +import logging.config +import logging.handlers +import platform + +LOG_LEVELS = { + -1: dict(root=logging.ERROR, mopidy=logging.WARNING), + 0: dict(root=logging.ERROR, mopidy=logging.INFO), + 1: dict(root=logging.WARNING, mopidy=logging.DEBUG), + 2: dict(root=logging.INFO, mopidy=logging.DEBUG), + 3: dict(root=logging.DEBUG, mopidy=logging.DEBUG), + 4: dict(root=logging.NOTSET, mopidy=logging.NOTSET), +} + +# Custom log level which has even lower priority than DEBUG +TRACE_LOG_LEVEL = 5 +logging.addLevelName(TRACE_LOG_LEVEL, "TRACE") + +logger = logging.getLogger(__name__) + + +class DelayedHandler(logging.Handler): + def __init__(self): + logging.Handler.__init__(self) + self._released = False + self._buffer = [] + + def handle(self, record): + if not self._released: + self._buffer.append(record) + + def release(self): + self._released = True + root = logging.getLogger("") + while self._buffer: + root.handle(self._buffer.pop(0)) + + +_delayed_handler = DelayedHandler() + + +def bootstrap_delayed_logging(): + root = logging.getLogger("") + root.setLevel(logging.NOTSET) + root.addHandler(_delayed_handler) + + +def setup_logging(config, base_verbosity_level, args_verbosity_level): + logging.captureWarnings(True) + + if config["logging"]["config_file"]: + # Logging config from file must be read before other handlers are + # added. If not, the other handlers will have no effect. + try: + path = config["logging"]["config_file"] + logging.config.fileConfig(path, disable_existing_loggers=False) + except Exception as e: + # Catch everything as logging does not specify what can go wrong. + logger.error("Loading logging config %r failed. %s", path, e) + + loglevels = config.get("loglevels", {}) + + verbosity_level = get_verbosity_level( + config, base_verbosity_level, args_verbosity_level + ) + verbosity_filter = VerbosityFilter(verbosity_level, loglevels) + + formatter = logging.Formatter(config["logging"]["format"]) + + if config["logging"]["color"]: + handler = ColorizingStreamHandler(config.get("logcolors", {})) + else: + handler = logging.StreamHandler() + handler.addFilter(verbosity_filter) + handler.setFormatter(formatter) + + logging.getLogger("").addHandler(handler) + + _delayed_handler.release() + + +def get_verbosity_level(config, base_verbosity_level, args_verbosity_level): + if args_verbosity_level: + result = base_verbosity_level + args_verbosity_level + else: + result = base_verbosity_level + config["logging"]["verbosity"] + + if result < min(LOG_LEVELS.keys()): + result = min(LOG_LEVELS.keys()) + if result > max(LOG_LEVELS.keys()): + result = max(LOG_LEVELS.keys()) + + return result + + +class VerbosityFilter(logging.Filter): + def __init__(self, verbosity_level, loglevels): + self.verbosity_level = verbosity_level + self.loglevels = loglevels + + def filter(self, record): + for name, required_log_level in self.loglevels.items(): + if record.name == name or record.name.startswith(name + "."): + return record.levelno >= required_log_level + + if record.name.startswith("mopidy"): + required_log_level = LOG_LEVELS[self.verbosity_level]["mopidy"] + else: + required_log_level = LOG_LEVELS[self.verbosity_level]["root"] + return record.levelno >= required_log_level + + +#: Available log colors. +COLORS = [ + "black", + "red", + "green", + "yellow", + "blue", + "magenta", + "cyan", + "white", +] + + +class ColorizingStreamHandler(logging.StreamHandler): + + """ + Stream handler which colorizes the log using ANSI escape sequences. + + Does nothing on Windows, which doesn't support ANSI escape sequences. + + This implementation is based upon https://gist.github.com/vsajip/758430, + which is: + + Copyright (C) 2010-2012 Vinay Sajip. All rights reserved. + Licensed under the new BSD license. + """ + + # Map logging levels to (background, foreground, bold/intense) + level_map = { + TRACE_LOG_LEVEL: (None, "blue", False), + logging.DEBUG: (None, "blue", False), + logging.INFO: (None, "white", False), + logging.WARNING: (None, "yellow", False), + logging.ERROR: (None, "red", False), + logging.CRITICAL: ("red", "white", True), + } + # Map logger name to foreground colors + logger_map = {} + + csi = "\x1b[" + reset = "\x1b[0m" + + is_windows = platform.system() == "Windows" + + def __init__(self, logger_colors): + super().__init__() + self.logger_map = logger_colors + + @property + def is_tty(self): + isatty = getattr(self.stream, "isatty", None) + return isatty and isatty() + + def emit(self, record): + try: + message = self.format(record) + self.stream.write(message) + self.stream.write(getattr(self, "terminator", "\n")) + self.flush() + except Exception: + self.handleError(record) + + def format(self, record): + message = logging.StreamHandler.format(self, record) + if not self.is_tty or self.is_windows: + return message + for name, color in self.logger_map.items(): + if record.name.startswith(name): + return self.colorize(message, fg=color) + if record.levelno in self.level_map: + bg, fg, bold = self.level_map[record.levelno] + return self.colorize(message, bg=bg, fg=fg, bold=bold) + return message + + def colorize(self, message, bg=None, fg=None, bold=False): + params = [] + if bg in COLORS: + params.append(str(COLORS.index(bg) + 40)) + if fg in COLORS: + params.append(str(COLORS.index(fg) + 30)) + if bold: + params.append("1") + if params: + message = "".join( + (self.csi, ";".join(params), "m", message, self.reset) + ) + return message diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/models.py b/venv/lib/python3.7/site-packages/mopidy/internal/models.py new file mode 100644 index 0000000..77bb3de --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/models.py @@ -0,0 +1,142 @@ +from mopidy.internal import validation +from mopidy.models import Ref, TlTrack, fields +from mopidy.models.immutable import ValidatedImmutableObject + + +class HistoryTrack(ValidatedImmutableObject): + """ + A history track. Wraps a :class:`Ref` and its timestamp. + + :param timestamp: the timestamp + :type timestamp: int + :param track: the track reference + :type track: :class:`Ref` + """ + + # The timestamp. Read-only. + timestamp = fields.Integer() + + # The track reference. Read-only. + track = fields.Field(type=Ref) + + +class HistoryState(ValidatedImmutableObject): + """ + State of the history controller. + Internally used for save/load state. + + :param history: the track history + :type history: list of :class:`HistoryTrack` + """ + + # The tracks. Read-only. + history = fields.Collection(type=HistoryTrack, container=tuple) + + +class MixerState(ValidatedImmutableObject): + """ + State of the mixer controller. + Internally used for save/load state. + + :param volume: the volume + :type volume: int + :param mute: the mute state + :type mute: int + """ + + # The volume. Read-only. + volume = fields.Integer(min=0, max=100) + + # The mute state. Read-only. + mute = fields.Boolean(default=False) + + +class PlaybackState(ValidatedImmutableObject): + """ + State of the playback controller. + Internally used for save/load state. + + :param tlid: current track tlid + :type tlid: int + :param time_position: play position + :type time_position: int + :param state: playback state + :type state: :class:`validation.PLAYBACK_STATES` + """ + + # The tlid of current playing track. Read-only. + tlid = fields.Integer(min=1) + + # The playback position. Read-only. + time_position = fields.Integer(min=0) + + # The playback state. Read-only. + state = fields.Field(choices=validation.PLAYBACK_STATES) + + +class TracklistState(ValidatedImmutableObject): + + """ + State of the tracklist controller. + Internally used for save/load state. + + :param repeat: the repeat mode + :type repeat: bool + :param consume: the consume mode + :type consume: bool + :param random: the random mode + :type random: bool + :param single: the single mode + :type single: bool + :param next_tlid: the id for the next added track + :type next_tlid: int + :param tl_tracks: the list of tracks + :type tl_tracks: list of :class:`TlTrack` + """ + + # The repeat mode. Read-only. + repeat = fields.Boolean() + + # The consume mode. Read-only. + consume = fields.Boolean() + + # The random mode. Read-only. + random = fields.Boolean() + + # The single mode. Read-only. + single = fields.Boolean() + + # The id of the track to play. Read-only. + next_tlid = fields.Integer(min=0) + + # The list of tracks. Read-only. + tl_tracks = fields.Collection(type=TlTrack, container=tuple) + + +class CoreState(ValidatedImmutableObject): + + """ + State of all Core controller. + Internally used for save/load state. + + :param history: State of the history controller + :type history: :class:`HistorState` + :param mixer: State of the mixer controller + :type mixer: :class:`MixerState` + :param playback: State of the playback controller + :type playback: :class:`PlaybackState` + :param tracklist: State of the tracklist controller + :type tracklist: :class:`TracklistState` + """ + + # State of the history controller. + history = fields.Field(type=HistoryState) + + # State of the mixer controller. + mixer = fields.Field(type=MixerState) + + # State of the playback controller. + playback = fields.Field(type=PlaybackState) + + # State of the tracklist controller. + tracklist = fields.Field(type=TracklistState) diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/network.py b/venv/lib/python3.7/site-packages/mopidy/internal/network.py new file mode 100644 index 0000000..2667be4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/network.py @@ -0,0 +1,31 @@ +import logging +import re +import socket + +logger = logging.getLogger(__name__) + + +def try_ipv6_socket(): + """Determine if system really supports IPv6""" + if not socket.has_ipv6: + return False + try: + socket.socket(socket.AF_INET6).close() + return True + except OSError as exc: + logger.debug( + f"Platform supports IPv6, but socket creation failed, " + f"disabling: {exc}" + ) + return False + + +#: Boolean value that indicates if creating an IPv6 socket will succeed. +has_ipv6 = try_ipv6_socket() + + +def format_hostname(hostname): + """Format hostname for display.""" + if has_ipv6 and re.match(r"\d+.\d+.\d+.\d+", hostname) is not None: + hostname = f"::ffff:{hostname}" + return hostname diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/path.py b/venv/lib/python3.7/site-packages/mopidy/internal/path.py new file mode 100644 index 0000000..9493592 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/path.py @@ -0,0 +1,104 @@ +import logging +import pathlib +import re +import urllib + +from mopidy.internal import xdg + +logger = logging.getLogger(__name__) + + +XDG_DIRS = xdg.get_dirs() + + +def get_or_create_dir(dir_path): + dir_path = expand_path(dir_path) + if dir_path.is_file(): + raise OSError( + f"A file with the same name as the desired dir, " + f"{dir_path!r}, already exists." + ) + elif not dir_path.is_dir(): + logger.info(f"Creating dir {dir_path.as_uri()}") + dir_path.mkdir(mode=0o755, parents=True) + return dir_path + + +def get_or_create_file(file_path, mkdir=True, content=None): + file_path = expand_path(file_path) + if isinstance(content, str): + content = content.encode() + if mkdir: + get_or_create_dir(file_path.parent) + if not file_path.is_file(): + logger.info(f"Creating file {file_path.as_uri()}") + file_path.touch(exist_ok=False) + if content is not None: + file_path.write_bytes(content) + return file_path + + +def get_unix_socket_path(socket_path): + match = re.search("^unix:(.*)", socket_path) + if not match: + return None + return match.group(1) + + +def path_to_uri(path): + """ + Convert OS specific path to file:// URI. + + Accepts either unicode strings or bytestrings. The encoding of any + bytestring will be maintained so that :func:`uri_to_path` can return the + same bytestring. + + Returns a file:// URI as an unicode string. + """ + return pathlib.Path(path).as_uri() + + +def uri_to_path(uri): + """ + Convert an URI to a OS specific path. + """ + bytes_path = urllib.parse.unquote_to_bytes(urllib.parse.urlsplit(uri).path) + unicode_path = bytes_path.decode(errors="surrogateescape") + return pathlib.Path(unicode_path) + + +def expand_path(path): + if isinstance(path, bytes): + path = path.decode(errors="surrogateescape") + path = str(pathlib.Path(path)) + + for xdg_var, xdg_dir in XDG_DIRS.items(): + path = path.replace("$" + xdg_var, str(xdg_dir)) + if "$" in path: + return None + + return pathlib.Path(path).expanduser().resolve() + + +def is_path_inside_base_dir(path, base_path): + if isinstance(path, bytes): + path = path.decode(errors="surrogateescape") + if isinstance(base_path, bytes): + base_path = base_path.decode(errors="surrogateescape") + + path = pathlib.Path(path).resolve() + base_path = pathlib.Path(base_path).resolve() + + if path.is_file(): + # Use dir of file for prefix comparision, so we don't accept + # /tmp/foo.m3u as being inside /tmp/foo, simply because they have a + # common prefix, /tmp/foo, which matches the base path, /tmp/foo. + path = path.parent + + # Check if dir of file is the base path or a subdir + try: + path.relative_to(base_path) + except ValueError: + return False + else: + return True diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/playlists.py b/venv/lib/python3.7/site-packages/mopidy/internal/playlists.py new file mode 100644 index 0000000..8f2d202 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/playlists.py @@ -0,0 +1,137 @@ +import configparser +import io + +from mopidy.internal import validation + +import xml.etree.ElementTree as elementtree # noqa: N813 + + +def parse(data): + handlers = { + detect_extm3u_header: parse_extm3u, + detect_pls_header: parse_pls, + detect_asx_header: parse_asx, + detect_xspf_header: parse_xspf, + } + for detector, parser in handlers.items(): + if detector(data): + return list(parser(data)) + return list(parse_urilist(data)) # Fallback + + +def detect_extm3u_header(data): + return data[0:7].upper() == b"#EXTM3U" + + +def detect_pls_header(data): + return data[0:10].lower() == b"[playlist]" + + +def detect_xspf_header(data): + data = data[0:150] + if b"xspf" not in data.lower(): + return False + + try: + data = io.BytesIO(data) + for _event, element in elementtree.iterparse(data, events=["start"]): + return element.tag.lower() == "{http://xspf.org/ns/0/}playlist" + except elementtree.ParseError: + pass + return False + + +def detect_asx_header(data): + data = data[0:50] + if b"asx" not in data.lower(): + return False + + try: + data = io.BytesIO(data) + for _event, element in elementtree.iterparse(data, events=["start"]): + return element.tag.lower() == "asx" + except elementtree.ParseError: + pass + return False + + +def parse_extm3u(data): + # TODO: convert non URIs to file URIs. + found_header = False + for line in data.splitlines(): + if found_header or line.startswith(b"#EXTM3U"): + found_header = True + else: + continue + + if not line.strip() or line.startswith(b"#"): + continue + + try: + line = line.decode() + except UnicodeDecodeError: + continue + + yield line.strip() + + +def parse_pls(data): + # TODO: convert non URIs to file URIs. + try: + cp = configparser.RawConfigParser() + cp.read_string(data.decode()) + except configparser.Error: + return + + for section in cp.sections(): + if section.lower() != "playlist": + continue + for i in range(cp.getint(section, "numberofentries")): + yield cp.get(section, f"file{i + 1}").strip("\"'") + + +def parse_xspf(data): + try: + # Last element will be root. + for _event, element in elementtree.iterparse(io.BytesIO(data)): + element.tag = element.tag.lower() # normalize + except elementtree.ParseError: + return + + ns = "http://xspf.org/ns/0/" + path = f"{{{ns}}}tracklist/{{{ns}}}track" + for track in element.iterfind(path): + yield track.findtext(f"{{{ns}}}location") + + +def parse_asx(data): + try: + # Last element will be root. + for _event, element in elementtree.iterparse(io.BytesIO(data)): + element.tag = element.tag.lower() # normalize + except elementtree.ParseError: + return + + for ref in element.findall("entry/ref[@href]"): + yield ref.get("href", "").strip() + + for entry in element.findall("entry[@href]"): + yield entry.get("href", "").strip() + + +def parse_urilist(data): + for line in data.splitlines(): + if not line.strip() or line.startswith(b"#"): + continue + + try: + line = line.decode() + except UnicodeDecodeError: + continue + + try: + validation.check_uri(line) + except ValueError: + continue + + yield line.strip() diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/process.py b/venv/lib/python3.7/site-packages/mopidy/internal/process.py new file mode 100644 index 0000000..3e976e7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/process.py @@ -0,0 +1,53 @@ +import logging +import threading + +import pykka + +import _thread + +logger = logging.getLogger(__name__) + + +def exit_process(): + logger.debug("Interrupting main...") + _thread.interrupt_main() + logger.debug("Interrupted main") + + +def sigterm_handler(signum, frame): + """A :mod:`signal` handler which will exit the program on signal. + + This function is not called when the process' main thread is running a GLib + mainloop. In that case, the GLib mainloop must listen for SIGTERM signals + and quit itself. + + For Mopidy subcommands that does not run the GLib mainloop, this handler + ensures a proper shutdown of the process on SIGTERM. + """ + logger.info("Got SIGTERM signal. Exiting...") + exit_process() + + +def stop_actors_by_class(klass): + actors = pykka.ActorRegistry.get_by_class(klass) + logger.debug("Stopping %d instance(s) of %s", len(actors), klass.__name__) + for actor in actors: + actor.stop() + + +def stop_remaining_actors(): + num_actors = len(pykka.ActorRegistry.get_all()) + while num_actors: + logger.error( + "There are actor threads still running, this is probably a bug" + ) + logger.debug( + "Seeing %d actor and %d non-actor thread(s): %s", + num_actors, + threading.active_count() - num_actors, + ", ".join([t.name for t in threading.enumerate()]), + ) + logger.debug("Stopping %d actor(s)...", num_actors) + pykka.ActorRegistry.stop_all() + num_actors = len(pykka.ActorRegistry.get_all()) + logger.debug("All actors stopped.") diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/storage.py b/venv/lib/python3.7/site-packages/mopidy/internal/storage.py new file mode 100644 index 0000000..b723de9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/storage.py @@ -0,0 +1,59 @@ +import gzip +import json +import logging +import pathlib +import tempfile + +from mopidy import models + +logger = logging.getLogger(__name__) + + +def load(path): + """ + Deserialize data from file. + + :param path: full path to import file + :type path: pathlib.Path + :return: deserialized data + :rtype: dict + """ + + # TODO: raise an exception in case of error? + if not path.is_file(): + logger.info("File does not exist: %s", path) + return {} + try: + with gzip.open(str(path), "rb") as fp: + return json.load(fp, object_hook=models.model_json_decoder) + except (OSError, ValueError) as exc: + logger.warning(f"Loading JSON failed: {exc}") + return {} + + +def dump(path, data): + """ + Serialize data to file. + + :param path: full path to export file + :type path: pathlib.Path + :param data: dictionary containing data to save + :type data: dict + """ + + # TODO: cleanup directory/basename.* files. + tmp = tempfile.NamedTemporaryFile( + prefix=path.name + ".", dir=str(path.parent), delete=False + ) + tmp_path = pathlib.Path(tmp.name) + + try: + data_string = json.dumps( + data, cls=models.ModelJSONEncoder, indent=2, separators=(",", ": ") + ) + with gzip.GzipFile(fileobj=tmp, mode="wb") as fp: + fp.write(data_string.encode()) + tmp_path.rename(path) + finally: + if tmp_path.exists(): + tmp_path.unlink() diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/timer.py b/venv/lib/python3.7/site-packages/mopidy/internal/timer.py new file mode 100644 index 0000000..fb2d104 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/timer.py @@ -0,0 +1,14 @@ +import contextlib +import logging +import time + +from mopidy.internal import log + +logger = logging.getLogger(__name__) + + +@contextlib.contextmanager +def time_logger(name, level=log.TRACE_LOG_LEVEL): + start = time.time() + yield + logger.log(level, "%s took %dms", name, (time.time() - start) * 1000) diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/validation.py b/venv/lib/python3.7/site-packages/mopidy/internal/validation.py new file mode 100644 index 0000000..82feed1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/validation.py @@ -0,0 +1,133 @@ +import urllib +from collections.abc import Iterable, Mapping + +from mopidy import exceptions + +PLAYBACK_STATES = {"paused", "stopped", "playing"} + +SEARCH_FIELDS = { + "uri", + "track_name", + "album", + "artist", + "albumartist", + "composer", + "performer", + "track_no", + "genre", + "date", + "comment", + "any", +} + +PLAYLIST_FIELDS = {"uri", "name"} # TODO: add length and last_modified? + +TRACKLIST_FIELDS = { # TODO: add bitrate, length, disc_no, track_no, modified? + "uri", + "name", + "genre", + "date", + "comment", + "musicbrainz_id", +} + +DISTINCT_FIELDS = { + "track", + "artist", + "albumartist", + "album", + "composer", + "performer", + "date", + "genre", +} + + +# TODO: _check_iterable(check, msg, **kwargs) + [check(a) for a in arg]? +def _check_iterable(arg, msg, **kwargs): + """Ensure we have an iterable which is not a string or an iterator""" + if isinstance(arg, str): + raise exceptions.ValidationError(msg.format(arg=arg, **kwargs)) + elif not isinstance(arg, Iterable): + raise exceptions.ValidationError(msg.format(arg=arg, **kwargs)) + elif iter(arg) is iter(arg): + raise exceptions.ValidationError(msg.format(arg=arg, **kwargs)) + + +def check_choice(arg, choices, msg="Expected one of {choices}, not {arg!r}"): + if arg not in choices: + raise exceptions.ValidationError( + msg.format(arg=arg, choices=tuple(choices)) + ) + + +def check_boolean(arg, msg="Expected a boolean, not {arg!r}"): + check_instance(arg, bool, msg=msg) + + +def check_instance(arg, cls, msg="Expected a {name} instance, not {arg!r}"): + if not isinstance(arg, cls): + raise exceptions.ValidationError(msg.format(arg=arg, name=cls.__name__)) + + +def check_instances(arg, cls, msg="Expected a list of {name}, not {arg!r}"): + _check_iterable(arg, msg, name=cls.__name__) + if not all(isinstance(instance, cls) for instance in arg): + raise exceptions.ValidationError(msg.format(arg=arg, name=cls.__name__)) + + +def check_integer(arg, min=None, max=None): + if not isinstance(arg, int): + raise exceptions.ValidationError(f"Expected an integer, not {arg!r}") + elif min is not None and arg < min: + raise exceptions.ValidationError( + f"Expected number larger or equal to {min}, not {arg!r}" + ) + elif max is not None and arg > max: + raise exceptions.ValidationError( + f"Expected number smaller or equal to {max}, not {arg!r}" + ) + + +def check_query(arg, fields=SEARCH_FIELDS, list_values=True): + # TODO: normalize name -> track_name + # TODO: normalize value -> [value] + # TODO: normalize blank -> [] or just remove field? + # TODO: remove list_values? + + if not isinstance(arg, Mapping): + raise exceptions.ValidationError( + f"Expected a query dictionary, not {arg!r}" + ) + + for key, value in arg.items(): + check_choice( + key, + fields, + msg="Expected query field to be one of " "{choices}, not {arg!r}", + ) + if list_values: + msg = 'Expected "{key}" to be list of strings, not {arg!r}' + _check_iterable(value, msg, key=key) + [_check_query_value(key, v, msg) for v in value] + else: + _check_query_value( + key, value, 'Expected "{key}" to be a string, not {arg!r}' + ) + + +def _check_query_value(key, arg, msg): + if not isinstance(arg, str) or not arg.strip(): + raise exceptions.ValidationError(msg.format(arg=arg, key=key)) + + +def check_uri(arg, msg="Expected a valid URI, not {arg!r}"): + if not isinstance(arg, str): + raise exceptions.ValidationError(msg.format(arg=arg)) + elif urllib.parse.urlparse(arg).scheme == "": + raise exceptions.ValidationError(msg.format(arg=arg)) + + +def check_uris(arg, msg="Expected a list of URIs, not {arg!r}"): + _check_iterable(arg, msg) + [check_uri(a, msg) for a in arg] diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/versioning.py b/venv/lib/python3.7/site-packages/mopidy/internal/versioning.py new file mode 100644 index 0000000..c9406ee --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/versioning.py @@ -0,0 +1,29 @@ +import os +import subprocess + +import mopidy + + +def get_version(): + try: + return get_git_version() + except OSError: + return mopidy.__version__ + + +def get_git_version(): + project_dir = os.path.abspath( + os.path.join(os.path.dirname(mopidy.__file__), "..") + ) + process = subprocess.Popen( + ["git", "describe"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=project_dir, + ) + if process.wait() != 0: + raise OSError('Execution of "git describe" failed') + version = process.stdout.read().strip().decode() + if version.startswith("v"): + version = version[1:] + return version diff --git a/venv/lib/python3.7/site-packages/mopidy/internal/xdg.py b/venv/lib/python3.7/site-packages/mopidy/internal/xdg.py new file mode 100644 index 0000000..66e732a --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/internal/xdg.py @@ -0,0 +1,68 @@ +import configparser +import os +import pathlib + + +def get_dirs(): + """Returns a dict of all the known XDG Base Directories for the current user. + + The keys ``XDG_CACHE_DIR``, ``XDG_CONFIG_DIR``, and ``XDG_DATA_DIR`` is + always available. + + Additional keys, like ``XDG_MUSIC_DIR``, may be available if the + ``$XDG_CONFIG_DIR/user-dirs.dirs`` file exists and is parseable. + + See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + for the XDG Base Directory specification. + """ + + dirs = { + "XDG_CACHE_DIR": pathlib.Path( + os.getenv("XDG_CACHE_HOME", "~/.cache") + ).expanduser(), + "XDG_CONFIG_DIR": pathlib.Path( + os.getenv("XDG_CONFIG_HOME", "~/.config") + ).expanduser(), + "XDG_DATA_DIR": pathlib.Path( + os.getenv("XDG_DATA_HOME", "~/.local/share") + ).expanduser(), + } + + dirs.update(_get_user_dirs(dirs["XDG_CONFIG_DIR"])) + + return dirs + + +def _get_user_dirs(xdg_config_dir): + """Returns a dict of XDG dirs read from + ``$XDG_CONFIG_HOME/user-dirs.dirs``. + + This is used at import time for most users of :mod:`mopidy`. By rolling our + own implementation instead of using :meth:`glib.get_user_special_dir` we + make it possible for many extensions to run their test suites, which are + importing parts of :mod:`mopidy`, in a virtualenv with global site-packages + disabled, and thus no :mod:`glib` available. + """ + + dirs_file = xdg_config_dir / "user-dirs.dirs" + + if not dirs_file.exists(): + return {} + + data = dirs_file.read_bytes() + data = b"[XDG_USER_DIRS]\n" + data + data = data.replace(b"$HOME", bytes(pathlib.Path.home())) + data = data.replace(b'"', b"") + + config = configparser.RawConfigParser() + config.read_string(data.decode()) + + result = {} + for k, v in config.items("XDG_USER_DIRS"): + if v is None: + continue + if isinstance(k, bytes): + k = k.decode() + result[k.upper()] = pathlib.Path(v).resolve() + + return result diff --git a/venv/lib/python3.7/site-packages/mopidy/listener.py b/venv/lib/python3.7/site-packages/mopidy/listener.py new file mode 100644 index 0000000..a302e9a --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/listener.py @@ -0,0 +1,45 @@ +import logging + +import pykka +from pykka.messages import ProxyCall + +logger = logging.getLogger(__name__) + + +def send(cls, event, **kwargs): + listeners = pykka.ActorRegistry.get_by_class(cls) + logger.debug("Sending %s to %s: %s", event, cls.__name__, kwargs) + for listener in listeners: + # Save time by calling methods on Pykka actor without creating a + # throwaway actor proxy. + # + # Because we use `.tell()` there is no return channel for any errors, + # so Pykka logs them immediately. The alternative would be to use + # `.ask()` and `.get()` the returned futures to block for the listeners + # to react and return their exceptions to us. Since emitting events in + # practise is making calls upwards in the stack, blocking here would + # quickly deadlock. + listener.tell( + ProxyCall(attr_path=["on_event"], args=[event], kwargs=kwargs,) + ) + + +class Listener: + def on_event(self, event, **kwargs): + """ + Called on all events. + + *MAY* be implemented by actor. By default, this method forwards the + event to the specific event methods. + + :param event: the event name + :type event: string + :param kwargs: any other arguments to the specific event handlers + """ + try: + getattr(self, event)(**kwargs) + except Exception: + # Ensure we don't crash the actor due to "bad" events. + logger.exception( + "Triggering event failed: %s(%s)", event, ", ".join(kwargs) + ) diff --git a/venv/lib/python3.7/site-packages/mopidy/local.py b/venv/lib/python3.7/site-packages/mopidy/local.py new file mode 100644 index 0000000..7bd95dd --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/local.py @@ -0,0 +1,13 @@ +# XXX This module is only here to ease the migration from Mopidy-Local being +# bundled with Mopidy to being an independent extension. This file should +# probably be removed before Mopidy 3.0 final ships. + +import warnings + +from mopidy_local import * # noqa + +warnings.warn( + "Mopidy-Local has been moved to its own project. " + "Update any imports from `mopidy.local` to use `mopidy_local` instead.", + DeprecationWarning, +) diff --git a/venv/lib/python3.7/site-packages/mopidy/m3u/__init__.py b/venv/lib/python3.7/site-packages/mopidy/m3u/__init__.py new file mode 100644 index 0000000..ab6f755 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/m3u/__init__.py @@ -0,0 +1,31 @@ +import logging +import os + +import mopidy +from mopidy import config, ext + +logger = logging.getLogger(__name__) + + +class Extension(ext.Extension): + + dist_name = "Mopidy-M3U" + ext_name = "m3u" + version = mopidy.__version__ + + def get_default_config(self): + conf_file = os.path.join(os.path.dirname(__file__), "ext.conf") + return config.read(conf_file) + + def get_config_schema(self): + schema = super().get_config_schema() + schema["base_dir"] = config.Path(optional=True) + schema["default_encoding"] = config.String() + schema["default_extension"] = config.String(choices=[".m3u", ".m3u8"]) + schema["playlists_dir"] = config.Path(optional=True) + return schema + + def setup(self, registry): + from .backend import M3UBackend + + registry.add("backend", M3UBackend) diff --git a/venv/lib/python3.7/site-packages/mopidy/m3u/backend.py b/venv/lib/python3.7/site-packages/mopidy/m3u/backend.py new file mode 100644 index 0000000..7047852 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/m3u/backend.py @@ -0,0 +1,13 @@ +import pykka + +from mopidy import backend + +from . import playlists + + +class M3UBackend(pykka.ThreadingActor, backend.Backend): + uri_schemes = ["m3u"] + + def __init__(self, config, audio): + super().__init__() + self.playlists = playlists.M3UPlaylistsProvider(self, config) diff --git a/venv/lib/python3.7/site-packages/mopidy/m3u/ext.conf b/venv/lib/python3.7/site-packages/mopidy/m3u/ext.conf new file mode 100644 index 0000000..16291c8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/m3u/ext.conf @@ -0,0 +1,6 @@ +[m3u] +enabled = true +playlists_dir = +base_dir = $XDG_MUSIC_DIR +default_encoding = latin-1 +default_extension = .m3u8 diff --git a/venv/lib/python3.7/site-packages/mopidy/m3u/playlists.py b/venv/lib/python3.7/site-packages/mopidy/m3u/playlists.py new file mode 100644 index 0000000..6f89d95 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/m3u/playlists.py @@ -0,0 +1,174 @@ +import contextlib +import locale +import logging +import operator +import os +import pathlib +import tempfile + +from mopidy import backend +from mopidy.internal import path + +from . import Extension, translator + +logger = logging.getLogger(__name__) + + +def log_environment_error(message, error): + if isinstance(error.strerror, bytes): + strerror = error.strerror.decode(locale.getpreferredencoding()) + else: + strerror = error.strerror + logger.error("%s: %s", message, strerror) + + +@contextlib.contextmanager +def replace(path, mode="w+b", encoding=None, errors=None): + (fd, tempname) = tempfile.mkstemp(dir=str(path.parent)) + tempname = pathlib.Path(tempname) + try: + fp = open(fd, mode, encoding=encoding, errors=errors) + except Exception: + tempname.unlink() + os.close(fd) + raise + try: + yield fp + fp.flush() + os.fsync(fd) + tempname.rename(path) + except Exception: + tempname.unlink() + raise + finally: + fp.close() + + +class M3UPlaylistsProvider(backend.PlaylistsProvider): + def __init__(self, backend, config): + super().__init__(backend) + + ext_config = config[Extension.ext_name] + if ext_config["playlists_dir"] is None: + self._playlists_dir = Extension.get_data_dir(config) + else: + self._playlists_dir = path.expand_path(ext_config["playlists_dir"]) + if ext_config["base_dir"] is None: + self._base_dir = self._playlists_dir + else: + self._base_dir = path.expand_path(ext_config["base_dir"]) + self._default_encoding = ext_config["default_encoding"] + self._default_extension = ext_config["default_extension"] + + def as_list(self): + result = [] + for entry in self._playlists_dir.iterdir(): + if entry.suffix not in [".m3u", ".m3u8"]: + continue + elif not entry.is_file(): + continue + else: + playlist_path = entry.relative_to(self._playlists_dir) + result.append(translator.path_to_ref(playlist_path)) + result.sort(key=operator.attrgetter("name")) + return result + + def create(self, name): + path = translator.path_from_name(name.strip(), self._default_extension) + try: + with self._open(path, "w"): + pass + mtime = self._abspath(path).stat().st_mtime + except OSError as e: + log_environment_error(f"Error creating playlist {name!r}", e) + else: + return translator.playlist(path, [], mtime) + + def delete(self, uri): + path = translator.uri_to_path(uri) + if not self._is_in_basedir(path): + logger.debug("Ignoring path outside playlist dir: %s", uri) + return False + try: + self._abspath(path).unlink() + except OSError as e: + log_environment_error(f"Error deleting playlist {uri!r}", e) + return False + else: + return True + + def get_items(self, uri): + path = translator.uri_to_path(uri) + if not self._is_in_basedir(path): + logger.debug("Ignoring path outside playlist dir: %s", uri) + return None + try: + with self._open(path, "r") as fp: + items = translator.load_items(fp, self._base_dir) + except OSError as e: + log_environment_error(f"Error reading playlist {uri!r}", e) + else: + return items + + def lookup(self, uri): + path = translator.uri_to_path(uri) + if not self._is_in_basedir(path): + logger.debug("Ignoring path outside playlist dir: %s", uri) + return None + try: + with self._open(path, "r") as fp: + items = translator.load_items(fp, self._base_dir) + mtime = self._abspath(path).stat().st_mtime + except OSError as e: + log_environment_error(f"Error reading playlist {uri!r}", e) + else: + return translator.playlist(path, items, mtime) + + def refresh(self): + pass # nothing to do + + def save(self, playlist): + path = translator.uri_to_path(playlist.uri) + if not self._is_in_basedir(path): + logger.debug("Ignoring path outside playlist dir: %s", playlist.uri) + return None + name = translator.name_from_path(path) + try: + with self._open(path, "w") as fp: + translator.dump_items(playlist.tracks, fp) + if playlist.name and playlist.name != name: + orig_path = path + path = translator.path_from_name(playlist.name.strip()) + path = path.with_suffix(orig_path.suffix) + self._abspath(orig_path).rename(self._abspath(path)) + mtime = self._abspath(path).stat().st_mtime + except OSError as e: + log_environment_error(f"Error saving playlist {playlist.uri!r}", e) + else: + return translator.playlist(path, playlist.tracks, mtime) + + def _abspath(self, path): + if not path.is_absolute(): + return self._playlists_dir / path + else: + return path + + def _is_in_basedir(self, local_path): + local_path = self._abspath(local_path) + return path.is_path_inside_base_dir(local_path, self._playlists_dir) + + def _open(self, path, mode="r"): + if path.suffix == ".m3u8": + encoding = "utf-8" + else: + encoding = self._default_encoding + if not path.is_absolute(): + path = self._abspath(path) + if not self._is_in_basedir(path): + raise Exception( + f"Path {path!r} is not inside playlist dir {self._playlist_dir!r}" + ) + if "w" in mode: + return replace(path, mode, encoding=encoding, errors="replace") + else: + return path.open(mode, encoding=encoding, errors="replace") diff --git a/venv/lib/python3.7/site-packages/mopidy/m3u/translator.py b/venv/lib/python3.7/site-packages/mopidy/m3u/translator.py new file mode 100644 index 0000000..31afcf3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/m3u/translator.py @@ -0,0 +1,87 @@ +import os +import pathlib +import urllib + +from mopidy import models +from mopidy.internal import path + +from . import Extension + + +def path_to_uri(path, scheme=Extension.ext_name): + """Convert file path to URI.""" + bytes_path = os.path.normpath(bytes(path)) + uripath = urllib.parse.quote_from_bytes(bytes_path) + return urllib.parse.urlunsplit((scheme, None, uripath, None, None)) + + +def uri_to_path(uri): + """Convert URI to file path.""" + return path.uri_to_path(uri) + + +def name_from_path(path): + """Extract name from file path.""" + name = bytes(pathlib.Path(path.stem)) + try: + return name.decode(errors="replace") + except UnicodeError: + return None + + +def path_from_name(name, ext=None, sep="|"): + """Convert name with optional extension to file path.""" + if ext: + name = name.replace(os.sep, sep) + ext + else: + name = name.replace(os.sep, sep) + return pathlib.Path(name) + + +def path_to_ref(path): + return models.Ref.playlist(uri=path_to_uri(path), name=name_from_path(path)) + + +def load_items(fp, basedir): + refs = [] + name = None + for line in filter(None, (line.strip() for line in fp)): + if line.startswith("#"): + if line.startswith("#EXTINF:"): + name = line.partition(",")[2] + continue + elif not urllib.parse.urlsplit(line).scheme: + path = basedir / line + if not name: + name = name_from_path(path) + uri = path_to_uri(path, scheme="file") + else: + # TODO: ensure this is urlencoded + uri = line # do *not* extract name from (stream?) URI path + refs.append(models.Ref.track(uri=uri, name=name)) + name = None + return refs + + +def dump_items(items, fp): + if any(item.name for item in items): + print("#EXTM3U", file=fp) + for item in items: + if item.name: + print(f"#EXTINF:-1,{item.name}", file=fp) + # TODO: convert file URIs to (relative) paths? + if isinstance(item.uri, bytes): + print(item.uri.decode(), file=fp) + else: + print(item.uri, file=fp) + + +def playlist(path, items=None, mtime=None): + if items is None: + items = [] + return models.Playlist( + uri=path_to_uri(path), + name=name_from_path(path), + tracks=[models.Track(uri=item.uri, name=item.name) for item in items], + last_modified=(int(mtime * 1000) if mtime else None), + ) diff --git a/venv/lib/python3.7/site-packages/mopidy/mixer.py b/venv/lib/python3.7/site-packages/mopidy/mixer.py new file mode 100644 index 0000000..3ae6389 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/mixer.py @@ -0,0 +1,152 @@ +import logging + +from mopidy import listener + +logger = logging.getLogger(__name__) + + +class Mixer: + + """ + Audio mixer API + + If the mixer has problems during initialization it should raise + :exc:`mopidy.exceptions.MixerError` with a descriptive error message. This + will make Mopidy print the error message and exit so that the user can fix + the issue. + + :param config: the entire Mopidy configuration + :type config: dict + """ + + name = None + """ + Name of the mixer. + + Used when configuring what mixer to use. Should match the + :attr:`~mopidy.ext.Extension.ext_name` of the extension providing the + mixer. + """ + + def __init__(self, config): + pass + + def get_volume(self): + """ + Get volume level of the mixer on a linear scale from 0 to 100. + + Example values: + + 0: + Minimum volume, usually silent. + 100: + Maximum volume. + :class:`None`: + Volume is unknown. + + *MAY be implemented by subclass.* + + :rtype: int in range [0..100] or :class:`None` + """ + return None + + def set_volume(self, volume): + """ + Set volume level of the mixer. + + *MAY be implemented by subclass.* + + :param volume: Volume in the range [0..100] + :type volume: int + :rtype: :class:`True` if success, :class:`False` if failure + """ + return False + + def trigger_volume_changed(self, volume): + """ + Send ``volume_changed`` event to all mixer listeners. + + This method should be called by subclasses when the volume is changed, + either because of a call to :meth:`set_volume` or because of any + external entity changing the volume. + """ + logger.debug("Mixer event: volume_changed(volume=%d)", volume) + MixerListener.send("volume_changed", volume=volume) + + def get_mute(self): + """ + Get mute state of the mixer. + + *MAY be implemented by subclass.* + + :rtype: :class:`True` if muted, :class:`False` if unmuted, + :class:`None` if unknown. + """ + return None + + def set_mute(self, mute): + """ + Mute or unmute the mixer. + + *MAY be implemented by subclass.* + + :param mute: :class:`True` to mute, :class:`False` to unmute + :type mute: bool + :rtype: :class:`True` if success, :class:`False` if failure + """ + return False + + def trigger_mute_changed(self, mute): + """ + Send ``mute_changed`` event to all mixer listeners. + + This method should be called by subclasses when the mute state is + changed, either because of a call to :meth:`set_mute` or because of + any external entity changing the mute state. + """ + logger.debug("Mixer event: mute_changed(mute=%s)", mute) + MixerListener.send("mute_changed", mute=mute) + + def ping(self): + """Called to check if the actor is still alive.""" + return True + + +class MixerListener(listener.Listener): + + """ + Marker interface for recipients of events sent by the mixer actor. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in the mixer actor. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + """ + + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of mixer listener events""" + listener.send(MixerListener, event, **kwargs) + + def volume_changed(self, volume): + """ + Called after the volume has changed. + + *MAY* be implemented by actor. + + :param volume: the new volume + :type volume: int in range [0..100] + """ + pass + + def mute_changed(self, mute): + """ + Called after the mute state has changed. + + *MAY* be implemented by actor. + + :param mute: :class:`True` if muted, :class:`False` if not muted + :type mute: bool + """ + pass diff --git a/venv/lib/python3.7/site-packages/mopidy/models/__init__.py b/venv/lib/python3.7/site-packages/mopidy/models/__init__.py new file mode 100644 index 0000000..9342783 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/models/__init__.py @@ -0,0 +1,361 @@ +from mopidy.models import fields +from mopidy.models.immutable import ImmutableObject, ValidatedImmutableObject +from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder + +__all__ = [ + "ImmutableObject", + "Ref", + "Image", + "Artist", + "Album", + "Track", + "TlTrack", + "Playlist", + "SearchResult", + "model_json_decoder", + "ModelJSONEncoder", + "ValidatedImmutableObject", +] + + +class Ref(ValidatedImmutableObject): + + """ + Model to represent URI references with a human friendly name and type + attached. This is intended for use a lightweight object "free" of metadata + that can be passed around instead of using full blown models. + + :param uri: object URI + :type uri: string + :param name: object name + :type name: string + :param type: object type + :type type: string + """ + + #: The object URI. Read-only. + uri = fields.URI() + + #: The object name. Read-only. + name = fields.String() + + #: The object type, e.g. "artist", "album", "track", "playlist", + #: "directory". Read-only. + type = fields.Identifier() # TODO: consider locking this down. + # type = fields.Field(choices=(ALBUM, ARTIST, DIRECTORY, PLAYLIST, TRACK)) + + #: Constant used for comparison with the :attr:`type` field. + ALBUM = "album" + + #: Constant used for comparison with the :attr:`type` field. + ARTIST = "artist" + + #: Constant used for comparison with the :attr:`type` field. + DIRECTORY = "directory" + + #: Constant used for comparison with the :attr:`type` field. + PLAYLIST = "playlist" + + #: Constant used for comparison with the :attr:`type` field. + TRACK = "track" + + @classmethod + def album(cls, **kwargs): + """Create a :class:`Ref` with ``type`` :attr:`ALBUM`.""" + kwargs["type"] = Ref.ALBUM + return cls(**kwargs) + + @classmethod + def artist(cls, **kwargs): + """Create a :class:`Ref` with ``type`` :attr:`ARTIST`.""" + kwargs["type"] = Ref.ARTIST + return cls(**kwargs) + + @classmethod + def directory(cls, **kwargs): + """Create a :class:`Ref` with ``type`` :attr:`DIRECTORY`.""" + kwargs["type"] = Ref.DIRECTORY + return cls(**kwargs) + + @classmethod + def playlist(cls, **kwargs): + """Create a :class:`Ref` with ``type`` :attr:`PLAYLIST`.""" + kwargs["type"] = Ref.PLAYLIST + return cls(**kwargs) + + @classmethod + def track(cls, **kwargs): + """Create a :class:`Ref` with ``type`` :attr:`TRACK`.""" + kwargs["type"] = Ref.TRACK + return cls(**kwargs) + + +class Image(ValidatedImmutableObject): + + """ + :param string uri: URI of the image + :param int width: Optional width of image or :class:`None` + :param int height: Optional height of image or :class:`None` + """ + + #: The image URI. Read-only. + uri = fields.URI() + + #: Optional width of the image or :class:`None`. Read-only. + width = fields.Integer(min=0) + + #: Optional height of the image or :class:`None`. Read-only. + height = fields.Integer(min=0) + + +class Artist(ValidatedImmutableObject): + + """ + :param uri: artist URI + :type uri: string + :param name: artist name + :type name: string + :param sortname: artist name for sorting + :type sortname: string + :param musicbrainz_id: MusicBrainz ID + :type musicbrainz_id: string + """ + + #: The artist URI. Read-only. + uri = fields.URI() + + #: The artist name. Read-only. + name = fields.String() + + #: Artist name for better sorting, e.g. with articles stripped + sortname = fields.String() + + #: The MusicBrainz ID of the artist. Read-only. + musicbrainz_id = fields.Identifier() + + +class Album(ValidatedImmutableObject): + + """ + :param uri: album URI + :type uri: string + :param name: album name + :type name: string + :param artists: album artists + :type artists: list of :class:`Artist` + :param num_tracks: number of tracks in album + :type num_tracks: integer or :class:`None` if unknown + :param num_discs: number of discs in album + :type num_discs: integer or :class:`None` if unknown + :param date: album release date (YYYY or YYYY-MM-DD) + :type date: string + :param musicbrainz_id: MusicBrainz ID + :type musicbrainz_id: string + """ + + #: The album URI. Read-only. + uri = fields.URI() + + #: The album name. Read-only. + name = fields.String() + + #: A set of album artists. Read-only. + artists = fields.Collection(type=Artist, container=frozenset) + + #: The number of tracks in the album. Read-only. + num_tracks = fields.Integer(min=0) + + #: The number of discs in the album. Read-only. + num_discs = fields.Integer(min=0) + + #: The album release date. Read-only. + date = fields.Date() + + #: The MusicBrainz ID of the album. Read-only. + musicbrainz_id = fields.Identifier() + + +class Track(ValidatedImmutableObject): + + """ + :param uri: track URI + :type uri: string + :param name: track name + :type name: string + :param artists: track artists + :type artists: list of :class:`Artist` + :param album: track album + :type album: :class:`Album` + :param composers: track composers + :type composers: list of :class:`Artist` + :param performers: track performers + :type performers: list of :class:`Artist` + :param genre: track genre + :type genre: string + :param track_no: track number in album + :type track_no: integer or :class:`None` if unknown + :param disc_no: disc number in album + :type disc_no: integer or :class:`None` if unknown + :param date: track release date (YYYY or YYYY-MM-DD) + :type date: string + :param length: track length in milliseconds + :type length: integer or :class:`None` if there is no duration + :param bitrate: bitrate in kbit/s + :type bitrate: integer + :param comment: track comment + :type comment: string + :param musicbrainz_id: MusicBrainz ID + :type musicbrainz_id: string + :param last_modified: Represents last modification time + :type last_modified: integer or :class:`None` if unknown + """ + + #: The track URI. Read-only. + uri = fields.URI() + + #: The track name. Read-only. + name = fields.String() + + #: A set of track artists. Read-only. + artists = fields.Collection(type=Artist, container=frozenset) + + #: The track :class:`Album`. Read-only. + album = fields.Field(type=Album) + + #: A set of track composers. Read-only. + composers = fields.Collection(type=Artist, container=frozenset) + + #: A set of track performers`. Read-only. + performers = fields.Collection(type=Artist, container=frozenset) + + #: The track genre. Read-only. + genre = fields.String() + + #: The track number in the album. Read-only. + track_no = fields.Integer(min=0) + + #: The disc number in the album. Read-only. + disc_no = fields.Integer(min=0) + + #: The track release date. Read-only. + date = fields.Date() + + #: The track length in milliseconds. Read-only. + length = fields.Integer(min=0) + + #: The track's bitrate in kbit/s. Read-only. + bitrate = fields.Integer(min=0) + + #: The track comment. Read-only. + comment = fields.String() + + #: The MusicBrainz ID of the track. Read-only. + musicbrainz_id = fields.Identifier() + + #: Integer representing when the track was last modified. Exact meaning + #: depends on source of track. For local files this is the modification + #: time in milliseconds since Unix epoch. For other backends it could be an + #: equivalent timestamp or simply a version counter. + last_modified = fields.Integer(min=0) + + +class TlTrack(ValidatedImmutableObject): + + """ + A tracklist track. Wraps a regular track and it's tracklist ID. + + The use of :class:`TlTrack` allows the same track to appear multiple times + in the tracklist. + + This class also accepts it's parameters as positional arguments. Both + arguments must be provided, and they must appear in the order they are + listed here. + + This class also supports iteration, so your extract its values like this:: + + (tlid, track) = tl_track + + :param tlid: tracklist ID + :type tlid: int + :param track: the track + :type track: :class:`Track` + """ + + #: The tracklist ID. Read-only. + tlid = fields.Integer(min=0) + + #: The track. Read-only. + track = fields.Field(type=Track) + + def __init__(self, *args, **kwargs): + if len(args) == 2 and len(kwargs) == 0: + kwargs["tlid"] = args[0] + kwargs["track"] = args[1] + args = [] + super().__init__(*args, **kwargs) + + def __iter__(self): + return iter([self.tlid, self.track]) + + +class Playlist(ValidatedImmutableObject): + + """ + :param uri: playlist URI + :type uri: string + :param name: playlist name + :type name: string + :param tracks: playlist's tracks + :type tracks: list of :class:`Track` elements + :param last_modified: + playlist's modification time in milliseconds since Unix epoch + :type last_modified: int + """ + + #: The playlist URI. Read-only. + uri = fields.URI() + + #: The playlist name. Read-only. + name = fields.String() + + #: The playlist's tracks. Read-only. + tracks = fields.Collection(type=Track, container=tuple) + + #: The playlist modification time in milliseconds since Unix epoch. + #: Read-only. + #: + #: Integer, or :class:`None` if unknown. + last_modified = fields.Integer(min=0) + + # TODO: def insert(self, pos, track): ... ? + + @property + def length(self): + """The number of tracks in the playlist. Read-only.""" + return len(self.tracks) + + +class SearchResult(ValidatedImmutableObject): + + """ + :param uri: search result URI + :type uri: string + :param tracks: matching tracks + :type tracks: list of :class:`Track` elements + :param artists: matching artists + :type artists: list of :class:`Artist` elements + :param albums: matching albums + :type albums: list of :class:`Album` elements + """ + + #: The search result URI. Read-only. + uri = fields.URI() + + #: The tracks matching the search query. Read-only. + tracks = fields.Collection(type=Track, container=tuple) + + #: The artists matching the search query. Read-only. + artists = fields.Collection(type=Artist, container=tuple) + + #: The albums matching the search query. Read-only. + albums = fields.Collection(type=Album, container=tuple) diff --git a/venv/lib/python3.7/site-packages/mopidy/models/fields.py b/venv/lib/python3.7/site-packages/mopidy/models/fields.py new file mode 100644 index 0000000..901aba6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/models/fields.py @@ -0,0 +1,179 @@ +import sys + + +class Field: + + """ + Base field for use in + :class:`~mopidy.models.immutable.ValidatedImmutableObject`. These fields + are responsible for type checking and other data sanitation in our models. + + For simplicity fields use the Python descriptor protocol to store the + values in the instance dictionary. Also note that fields are mutable if + the object they are attached to allow it. + + Default values will be validated with the exception of :class:`None`. + + :param default: default value for field + :param type: if set the field value must be of this type + :param choices: if set the field value must be one of these + """ + + def __init__(self, default=None, type=None, choices=None): + self._name = None # Set by ValidatedImmutableObjectMeta + self._choices = choices + self._default = default + self._type = type + + if self._default is not None: + self.validate(self._default) + + def validate(self, value): + """Validate and possibly modify the field value before assignment""" + if self._type and not isinstance(value, self._type): + raise TypeError( + f"Expected {self._name} to be a {self._type}, not {value!r}" + ) + if self._choices and value not in self._choices: + raise TypeError( + f"Expected {self._name} to be a one of {self._choices}, not {value!r}" + ) + return value + + def __get__(self, instance, owner): + if not instance: + return self + return getattr(instance, "_" + self._name, self._default) + + def __set__(self, instance, value): + if value is not None: + value = self.validate(value) + + if value is None or value == self._default: + self.__delete__(instance) + else: + setattr(instance, "_" + self._name, value) + + def __delete__(self, instance): + if hasattr(instance, "_" + self._name): + delattr(instance, "_" + self._name) + + +class String(Field): + + """ + Specialized :class:`Field` which is wired up for bytes and unicode. + + :param default: default value for field + """ + + def __init__(self, default=None): + # TODO: normalize to unicode? + # TODO: only allow unicode? + # TODO: disallow empty strings? + super().__init__(type=str, default=default) + + +class Date(String): + """ + :class:`Field` for storing ISO 8601 dates as a string. + + Supported formats are ``YYYY-MM-DD``, ``YYYY-MM`` and ``YYYY``, currently + not validated. + + :param default: default value for field + """ + + pass # TODO: make this check for YYYY-MM-DD, YYYY-MM, YYYY using strptime. + + +class Identifier(String): + """ + :class:`Field` for storing values such as GUIDs or other identifiers. + + Values will be interned. + + :param default: default value for field + """ + + def validate(self, value): + value = super().validate(value) + if isinstance(value, bytes): + value = value.decode() + return sys.intern(value) + + +class URI(Identifier): + """ + :class:`Field` for storing URIs + + Values will be interned, currently not validated. + + :param default: default value for field + """ + + pass # TODO: validate URIs? + + +class Integer(Field): + """ + :class:`Field` for storing integer numbers. + + :param default: default value for field + :param min: field value must be larger or equal to this value when set + :param max: field value must be smaller or equal to this value when set + """ + + def __init__(self, default=None, min=None, max=None): + self._min = min + self._max = max + super().__init__(type=int, default=default) + + def validate(self, value): + value = super().validate(value) + if self._min is not None and value < self._min: + raise ValueError( + f"Expected {self._name} to be at least {self._min}, not {value:d}" + ) + if self._max is not None and value > self._max: + raise ValueError( + f"Expected {self._name} to be at most {self._max}, not {value:d}" + ) + return value + + +class Boolean(Field): + """ + :class:`Field` for storing boolean values + + :param default: default value for field + """ + + def __init__(self, default=None): + super().__init__(type=bool, default=default) + + +class Collection(Field): + """ + :class:`Field` for storing collections of a given type. + + :param type: all items stored in the collection must be of this type + :param container: the type to store the items in + """ + + def __init__(self, type, container=tuple): + super().__init__(type=type, default=container()) + + def validate(self, value): + if isinstance(value, str): + raise TypeError( + f"Expected {self._name} to be a collection of " + f"{self._type.__name__}, not {value!r}" + ) + for v in value: + if not isinstance(v, self._type): + raise TypeError( + f"Expected {self._name} to be a collection of " + f"{self._type.__name__}, not {value!r}" + ) + return self._default.__class__(value) or None diff --git a/venv/lib/python3.7/site-packages/mopidy/models/immutable.py b/venv/lib/python3.7/site-packages/mopidy/models/immutable.py new file mode 100644 index 0000000..fbf29ed --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/models/immutable.py @@ -0,0 +1,219 @@ +import copy +import itertools +import weakref + +from mopidy.models.fields import Field + +# Registered models for automatic deserialization +_models = {} + + +class ImmutableObject: + """ + Superclass for immutable objects whose fields can only be modified via the + constructor. + + This version of this class has been retained to avoid breaking any clients + relying on it's behavior. Internally in Mopidy we now use + :class:`ValidatedImmutableObject` for type safety and it's much smaller + memory footprint. + + :param kwargs: kwargs to set as fields on the object + :type kwargs: any + """ + + # Any sub-classes that don't set slots won't be effected by the base using + # slots as they will still get an instance dict. + __slots__ = ["__weakref__"] + + def __init__(self, *args, **kwargs): + for key, value in kwargs.items(): + if not self._is_valid_field(key): + raise TypeError( + f"__init__() got an unexpected keyword argument {key!r}" + ) + self._set_field(key, value) + + def __setattr__(self, name, value): + if name.startswith("_"): + object.__setattr__(self, name, value) + else: + raise AttributeError("Object is immutable.") + + def __delattr__(self, name): + if name.startswith("_"): + object.__delattr__(self, name) + else: + raise AttributeError("Object is immutable.") + + def _is_valid_field(self, name): + return hasattr(self, name) and not callable(getattr(self, name)) + + def _set_field(self, name, value): + if value == getattr(self.__class__, name): + self.__dict__.pop(name, None) + else: + self.__dict__[name] = value + + def _items(self): + return self.__dict__.items() + + def __repr__(self): + kwarg_pairs = [] + for key, value in sorted(self._items()): + if isinstance(value, (frozenset, tuple)): + if not value: + continue + value = list(value) + kwarg_pairs.append(f"{key}={value!r}") + return f"{self.__class__.__name__}({', '.join(kwarg_pairs)})" + + def __hash__(self): + hash_sum = 0 + for key, value in self._items(): + hash_sum += hash(key) + hash(value) + return hash_sum + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return all( + a == b + for a, b in itertools.zip_longest( + self._items(), other._items(), fillvalue=object() + ) + ) + + def __ne__(self, other): + return not self.__eq__(other) + + def replace(self, **kwargs): + """ + Replace the fields in the model and return a new instance + + Examples:: + + # Returns a track with a new name + Track(name='foo').replace(name='bar') + # Return an album with a new number of tracks + Album(num_tracks=2).replace(num_tracks=5) + + :param kwargs: kwargs to set as fields on the object + :type kwargs: any + :rtype: instance of the model with replaced fields + """ + other = copy.copy(self) + for key, value in kwargs.items(): + if not self._is_valid_field(key): + raise TypeError( + f"replace() got an unexpected keyword argument {key!r}" + ) + other._set_field(key, value) + return other + + def serialize(self): + data = {} + data["__model__"] = self.__class__.__name__ + for key, value in self._items(): + if isinstance(value, (set, frozenset, list, tuple)): + value = [ + v.serialize() if isinstance(v, ImmutableObject) else v + for v in value + ] + elif isinstance(value, ImmutableObject): + value = value.serialize() + if not (isinstance(value, list) and len(value) == 0): + data[key] = value + return data + + +class _ValidatedImmutableObjectMeta(type): + + """Helper that initializes fields, slots and memoizes instance creation.""" + + def __new__(cls, name, bases, attrs): + fields = {} + + for base in bases: # Copy parent fields over to our state + fields.update(getattr(base, "_fields", {})) + + for key, value in attrs.items(): # Add our own fields + if isinstance(value, Field): + fields[key] = "_" + key + value._name = key + + attrs["_fields"] = fields + attrs["_instances"] = weakref.WeakValueDictionary() + attrs["__slots__"] = list(attrs.get("__slots__", [])) + list( + fields.values() + ) + + clsc = super().__new__(cls, name, bases, attrs) + + if clsc.__name__ != "ValidatedImmutableObject": + _models[clsc.__name__] = clsc + + return clsc + + def __call__(cls, *args, **kwargs): # noqa: N805 + instance = super().__call__(*args, **kwargs) + return cls._instances.setdefault(weakref.ref(instance), instance) + + +class ValidatedImmutableObject( + ImmutableObject, metaclass=_ValidatedImmutableObjectMeta +): + """ + Superclass for immutable objects whose fields can only be modified via the + constructor. Fields should be :class:`Field` instances to ensure type + safety in our models. + + Note that since these models can not be changed, we heavily memoize them + to save memory. So constructing a class with the same arguments twice will + give you the same instance twice. + """ + + __slots__ = ["_hash"] + + def __hash__(self): + if not hasattr(self, "_hash"): + hash_sum = super().__hash__() + object.__setattr__(self, "_hash", hash_sum) + return self._hash + + def _is_valid_field(self, name): + return name in self._fields + + def _set_field(self, name, value): + object.__setattr__(self, name, value) + + def _items(self): + for field, key in self._fields.items(): + if hasattr(self, key): + yield field, getattr(self, key) + + def replace(self, **kwargs): + """ + Replace the fields in the model and return a new instance + + Examples:: + + # Returns a track with a new name + Track(name='foo').replace(name='bar') + # Return an album with a new number of tracks + Album(num_tracks=2).replace(num_tracks=5) + + Note that internally we memoize heavily to keep memory usage down given + our overly repetitive data structures. So you might get an existing + instance if it contains the same values. + + :param kwargs: kwargs to set as fields on the object + :type kwargs: any + :rtype: instance of the model with replaced fields + """ + if not kwargs: + return self + other = super().replace(**kwargs) + if hasattr(self, "_hash"): + object.__delattr__(other, "_hash") + return self._instances.setdefault(weakref.ref(other), other) diff --git a/venv/lib/python3.7/site-packages/mopidy/models/serialize.py b/venv/lib/python3.7/site-packages/mopidy/models/serialize.py new file mode 100644 index 0000000..066f3ff --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/models/serialize.py @@ -0,0 +1,43 @@ +import json + +from mopidy.models import immutable + + +class ModelJSONEncoder(json.JSONEncoder): + + """ + Automatically serialize Mopidy models to JSON. + + Usage:: + + >>> import json + >>> json.dumps({'a_track': Track(name='name')}, cls=ModelJSONEncoder) + '{"a_track": {"__model__": "Track", "name": "name"}}' + + """ + + def default(self, obj): + if isinstance(obj, immutable.ImmutableObject): + return obj.serialize() + return json.JSONEncoder.default(self, obj) + + +def model_json_decoder(dct): + """ + Automatically deserialize Mopidy models from JSON. + + Usage:: + + >>> import json + >>> json.loads( + ... '{"a_track": {"__model__": "Track", "name": "name"}}', + ... object_hook=model_json_decoder) + {u'a_track': Track(artists=[], name=u'name')} + + """ + if "__model__" in dct: + model_name = dct.pop("__model__") + if model_name in immutable._models: + cls = immutable._models[model_name] + return cls(**dct) + return dct diff --git a/venv/lib/python3.7/site-packages/mopidy/softwaremixer/__init__.py b/venv/lib/python3.7/site-packages/mopidy/softwaremixer/__init__.py new file mode 100644 index 0000000..6966b4e --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/softwaremixer/__init__.py @@ -0,0 +1,24 @@ +import os + +import mopidy +from mopidy import config, ext + + +class Extension(ext.Extension): + + dist_name = "Mopidy-SoftwareMixer" + ext_name = "softwaremixer" + version = mopidy.__version__ + + def get_default_config(self): + conf_file = os.path.join(os.path.dirname(__file__), "ext.conf") + return config.read(conf_file) + + def get_config_schema(self): + schema = super().get_config_schema() + return schema + + def setup(self, registry): + from .mixer import SoftwareMixer + + registry.add("mixer", SoftwareMixer) diff --git a/venv/lib/python3.7/site-packages/mopidy/softwaremixer/ext.conf b/venv/lib/python3.7/site-packages/mopidy/softwaremixer/ext.conf new file mode 100644 index 0000000..47a98ba --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/softwaremixer/ext.conf @@ -0,0 +1,2 @@ +[softwaremixer] +enabled = true diff --git a/venv/lib/python3.7/site-packages/mopidy/softwaremixer/mixer.py b/venv/lib/python3.7/site-packages/mopidy/softwaremixer/mixer.py new file mode 100644 index 0000000..43f4055 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/softwaremixer/mixer.py @@ -0,0 +1,58 @@ +import logging + +import pykka + +from mopidy import mixer + +logger = logging.getLogger(__name__) + + +class SoftwareMixer(pykka.ThreadingActor, mixer.Mixer): + + name = "software" + + def __init__(self, config): + super().__init__(config) + + self._audio_mixer = None + self._initial_volume = None + self._initial_mute = None + + def setup(self, mixer_ref): + self._audio_mixer = mixer_ref + + # The Mopidy startup procedure will set the initial volume of a + # mixer, but this happens before the audio actor's mixer is injected + # into the software mixer actor and has no effect. Thus, we need to set + # the initial volume again. + if self._initial_volume is not None: + self.set_volume(self._initial_volume) + if self._initial_mute is not None: + self.set_mute(self._initial_mute) + + def teardown(self): + self._audio_mixer = None + + def get_volume(self): + if self._audio_mixer is None: + return None + return self._audio_mixer.get_volume().get() + + def set_volume(self, volume): + if self._audio_mixer is None: + self._initial_volume = volume + return False + self._audio_mixer.set_volume(volume) + return True + + def get_mute(self): + if self._audio_mixer is None: + return None + return self._audio_mixer.get_mute().get() + + def set_mute(self, mute): + if self._audio_mixer is None: + self._initial_mute = mute + return False + self._audio_mixer.set_mute(mute) + return True diff --git a/venv/lib/python3.7/site-packages/mopidy/stream/__init__.py b/venv/lib/python3.7/site-packages/mopidy/stream/__init__.py new file mode 100644 index 0000000..aaeaad1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/stream/__init__.py @@ -0,0 +1,30 @@ +import os + +import mopidy +from mopidy import config, ext + + +class Extension(ext.Extension): + + dist_name = "Mopidy-Stream" + ext_name = "stream" + version = mopidy.__version__ + + def get_default_config(self): + conf_file = os.path.join(os.path.dirname(__file__), "ext.conf") + return config.read(conf_file) + + def get_config_schema(self): + schema = super().get_config_schema() + schema["protocols"] = config.List() + schema["metadata_blacklist"] = config.List(optional=True) + schema["timeout"] = config.Integer(minimum=1000, maximum=1000 * 60 * 60) + return schema + + def validate_environment(self): + pass + + def setup(self, registry): + from .actor import StreamBackend + + registry.add("backend", StreamBackend) diff --git a/venv/lib/python3.7/site-packages/mopidy/stream/actor.py b/venv/lib/python3.7/site-packages/mopidy/stream/actor.py new file mode 100644 index 0000000..8c993fb --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/stream/actor.py @@ -0,0 +1,187 @@ +import fnmatch +import logging +import re +import time +import urllib + +import pykka + +from mopidy import audio as audio_lib +from mopidy import backend, exceptions, stream +from mopidy.audio import scan, tags +from mopidy.internal import http, playlists +from mopidy.models import Track + +logger = logging.getLogger(__name__) + + +class StreamBackend(pykka.ThreadingActor, backend.Backend): + def __init__(self, config, audio): + super().__init__() + + self._scanner = scan.Scanner( + timeout=config["stream"]["timeout"], proxy_config=config["proxy"] + ) + + self._session = http.get_requests_session( + proxy_config=config["proxy"], + user_agent=( + f"{stream.Extension.dist_name}/{stream.Extension.version}" + ), + ) + + blacklist = config["stream"]["metadata_blacklist"] + self._blacklist_re = re.compile( + r"^(%s)$" % "|".join(fnmatch.translate(u) for u in blacklist) + ) + + self._timeout = config["stream"]["timeout"] + + self.library = StreamLibraryProvider(backend=self) + self.playback = StreamPlaybackProvider(audio=audio, backend=self) + self.playlists = None + + self.uri_schemes = audio_lib.supported_uri_schemes( + config["stream"]["protocols"] + ) + + if "file" in self.uri_schemes and config["file"]["enabled"]: + logger.warning( + 'The stream/protocols config value includes the "file" ' + 'protocol. "file" playback is now handled by Mopidy-File. ' + "Please remove it from the stream/protocols config." + ) + self.uri_schemes -= {"file"} + + +class StreamLibraryProvider(backend.LibraryProvider): + def lookup(self, uri): + if urllib.parse.urlsplit(uri).scheme not in self.backend.uri_schemes: + return [] + + if self.backend._blacklist_re.match(uri): + logger.debug("URI matched metadata lookup blacklist: %s", uri) + return [Track(uri=uri)] + + _, scan_result = _unwrap_stream( + uri, + timeout=self.backend._timeout, + scanner=self.backend._scanner, + requests_session=self.backend._session, + ) + + if scan_result: + track = tags.convert_tags_to_track(scan_result.tags).replace( + uri=uri, length=scan_result.duration + ) + else: + logger.warning("Problem looking up %s", uri) + track = Track(uri=uri) + + return [track] + + +class StreamPlaybackProvider(backend.PlaybackProvider): + def translate_uri(self, uri): + if urllib.parse.urlsplit(uri).scheme not in self.backend.uri_schemes: + return None + + if self.backend._blacklist_re.match(uri): + logger.debug("URI matched metadata lookup blacklist: %s", uri) + return uri + + unwrapped_uri, _ = _unwrap_stream( + uri, + timeout=self.backend._timeout, + scanner=self.backend._scanner, + requests_session=self.backend._session, + ) + return unwrapped_uri + + +# TODO: cleanup the return value of this. +def _unwrap_stream(uri, timeout, scanner, requests_session): + """ + Get a stream URI from a playlist URI, ``uri``. + + Unwraps nested playlists until something that's not a playlist is found or + the ``timeout`` is reached. + """ + + original_uri = uri + seen_uris = set() + deadline = time.time() + timeout + + while time.time() < deadline: + if uri in seen_uris: + logger.info( + "Unwrapping stream from URI (%s) failed: " + "playlist referenced itself", + uri, + ) + return None, None + else: + seen_uris.add(uri) + + logger.debug("Unwrapping stream from URI: %s", uri) + + try: + scan_timeout = deadline - time.time() + if scan_timeout < 0: + logger.info( + "Unwrapping stream from URI (%s) failed: " + "timed out in %sms", + uri, + timeout, + ) + return None, None + scan_result = scanner.scan(uri, timeout=scan_timeout) + except exceptions.ScannerError as exc: + logger.debug("GStreamer failed scanning URI (%s): %s", uri, exc) + scan_result = None + + if scan_result is not None: + has_interesting_mime = ( + scan_result.mime is not None + and not scan_result.mime.startswith("text/") + and not scan_result.mime.startswith("application/") + ) + if scan_result.playable or has_interesting_mime: + logger.debug( + "Unwrapped potential %s stream: %s", scan_result.mime, uri + ) + return uri, scan_result + + download_timeout = deadline - time.time() + if download_timeout < 0: + logger.info( + "Unwrapping stream from URI (%s) failed: timed out in %sms", + uri, + timeout, + ) + return None, None + content = http.download( + requests_session, uri, timeout=download_timeout / 1000 + ) + + if content is None: + logger.info( + "Unwrapping stream from URI (%s) failed: " + "error downloading URI %s", + original_uri, + uri, + ) + return None, None + + uris = playlists.parse(content) + if not uris: + logger.debug( + "Failed parsing URI (%s) as playlist; found potential stream.", + uri, + ) + return uri, None + + # TODO Test streams and return first that seems to be playable + new_uri = uris[0] + logger.debug("Parsed playlist (%s) and found new URI: %s", uri, new_uri) + uri = urllib.parse.urljoin(uri, new_uri) diff --git a/venv/lib/python3.7/site-packages/mopidy/stream/ext.conf b/venv/lib/python3.7/site-packages/mopidy/stream/ext.conf new file mode 100644 index 0000000..928ccc6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/stream/ext.conf @@ -0,0 +1,11 @@ +[stream] +enabled = true +protocols = + http + https + mms + rtmp + rtmps + rtsp +timeout = 5000 +metadata_blacklist = diff --git a/venv/lib/python3.7/site-packages/mopidy/zeroconf.py b/venv/lib/python3.7/site-packages/mopidy/zeroconf.py new file mode 100644 index 0000000..9b57c99 --- /dev/null +++ b/venv/lib/python3.7/site-packages/mopidy/zeroconf.py @@ -0,0 +1,150 @@ +import logging +import string + +logger = logging.getLogger(__name__) + +try: + import dbus +except ImportError: + dbus = None + +_AVAHI_IF_UNSPEC = -1 +_AVAHI_PROTO_UNSPEC = -1 +_AVAHI_PUBLISHFLAGS_NONE = 0 + + +def _is_loopback_address(host): + return ( + host.startswith("127.") + or host.startswith("::ffff:127.") + or host == "::1" + ) + + +def _convert_text_list_to_dbus_format(text_list): + array = dbus.Array(signature="ay") + for text in text_list: + array.append([dbus.Byte(ord(c)) for c in text]) + return array + + +class Zeroconf: + + """Publish a network service with Zeroconf. + + Currently, this only works on Linux using Avahi via D-Bus. + + :param str name: human readable name of the service, e.g. 'MPD on neptune' + :param str stype: service type, e.g. '_mpd._tcp' + :param int port: TCP port of the service, e.g. 6600 + :param str domain: local network domain name, defaults to '' + :param str host: interface to advertise the service on, defaults to '' + :param text: extra information depending on ``stype``, defaults to empty + list + :type text: list of str + """ + + def __init__(self, name, stype, port, domain="", host="", text=None): + self.stype = stype + self.port = port + self.domain = domain + self.host = host + self.text = text or [] + + self.bus = None + self.server = None + self.group = None + self.display_hostname = None + self.name = None + + if dbus: + try: + self.bus = dbus.SystemBus() + self.server = dbus.Interface( + self.bus.get_object("org.freedesktop.Avahi", "/"), + "org.freedesktop.Avahi.Server", + ) + self.display_hostname = f"{self.server.GetHostName()}" + self.name = string.Template(name).safe_substitute( + hostname=self.display_hostname, port=port + ) + except dbus.exceptions.DBusException as e: + logger.debug("%s: Server failed: %s", self, e) + + def __str__(self): + return ( + f"Zeroconf service {self.name!r} " + f"({self.stype} at [{self.host}]:{self.port:d})" + ) + + def publish(self): + """Publish the service. + + Call when your service starts. + """ + + if _is_loopback_address(self.host): + logger.debug( + "%s: Publish on loopback interface is not supported.", self + ) + return False + + if not dbus: + logger.debug("%s: dbus not installed; publish failed.", self) + return False + + if not self.bus: + logger.debug("%s: Bus not available; publish failed.", self) + return False + + if not self.server: + logger.debug("%s: Server not available; publish failed.", self) + return False + + try: + if not self.bus.name_has_owner("org.freedesktop.Avahi"): + logger.debug( + "%s: Avahi service not running; publish failed.", self + ) + return False + + self.group = dbus.Interface( + self.bus.get_object( + "org.freedesktop.Avahi", self.server.EntryGroupNew() + ), + "org.freedesktop.Avahi.EntryGroup", + ) + + self.group.AddService( + _AVAHI_IF_UNSPEC, + _AVAHI_PROTO_UNSPEC, + dbus.UInt32(_AVAHI_PUBLISHFLAGS_NONE), + self.name, + self.stype, + self.domain, + self.host, + dbus.UInt16(self.port), + _convert_text_list_to_dbus_format(self.text), + ) + + self.group.Commit() + logger.debug("%s: Published", self) + return True + except dbus.exceptions.DBusException as e: + logger.debug("%s: Publish failed: %s", self, e) + return False + + def unpublish(self): + """Unpublish the service. + + Call when your service shuts down. + """ + + if self.group: + try: + self.group.Reset() + logger.debug("%s: Unpublished", self) + except dbus.exceptions.DBusException as e: + logger.debug("%s: Unpublish failed: %s", self, e) + finally: + self.group = None diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/PKG-INFO b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/PKG-INFO new file mode 100644 index 0000000..0b410a2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,73 @@ +Metadata-Version: 1.2 +Name: pip +Version: 19.0.3 +Summary: The PyPA recommended tool for installing Python packages. +Home-page: https://pip.pypa.io/ +Author: The pip developers +Author-email: pypa-dev@groups.google.com +License: MIT +Description: pip - The Python Package Installer + ================================== + + .. image:: https://img.shields.io/pypi/v/pip.svg + :target: https://pypi.org/project/pip/ + + .. image:: https://readthedocs.org/projects/pip/badge/?version=latest + :target: https://pip.pypa.io/en/latest + + pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes. + + Please take a look at our documentation for how to install and use pip: + + * `Installation`_ + * `Usage`_ + * `Release notes`_ + + If you find bugs, need help, or want to talk to the developers please use our mailing lists or chat rooms: + + * `Issue tracking`_ + * `Discourse channel`_ + * `User IRC`_ + + If you want to get involved head over to GitHub to get the source code and feel free to jump on the developer mailing lists and chat rooms: + + * `GitHub page`_ + * `Dev mailing list`_ + * `Dev IRC`_ + + Code of Conduct + --------------- + + Everyone interacting in the pip project's codebases, issue trackers, chat + rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. + + .. _package installer: https://packaging.python.org/en/latest/current/ + .. _Python Package Index: https://pypi.org + .. _Installation: https://pip.pypa.io/en/stable/installing.html + .. _Usage: https://pip.pypa.io/en/stable/ + .. _Release notes: https://pip.pypa.io/en/stable/news.html + .. _GitHub page: https://github.com/pypa/pip + .. _Issue tracking: https://github.com/pypa/pip/issues + .. _Discourse channel: https://discuss.python.org/c/packaging + .. _Dev mailing list: https://groups.google.com/forum/#!forum/pypa-dev + .. _User IRC: https://webchat.freenode.net/?channels=%23pypa + .. _Dev IRC: https://webchat.freenode.net/?channels=%23pypa-dev + .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ + +Keywords: distutils easy_install egg setuptools wheel virtualenv +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Build Tools +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/SOURCES.txt b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/SOURCES.txt new file mode 100644 index 0000000..eb4810d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/SOURCES.txt @@ -0,0 +1,391 @@ +AUTHORS.txt +LICENSE.txt +MANIFEST.in +NEWS.rst +README.rst +pyproject.toml +setup.cfg +setup.py +docs/pip_sphinxext.py +docs/html/conf.py +docs/html/cookbook.rst +docs/html/index.rst +docs/html/installing.rst +docs/html/logic.rst +docs/html/news.rst +docs/html/quickstart.rst +docs/html/usage.rst +docs/html/user_guide.rst +docs/html/development/configuration.rst +docs/html/development/contributing.rst +docs/html/development/getting-started.rst +docs/html/development/index.rst +docs/html/development/release-process.rst +docs/html/development/vendoring-policy.rst +docs/html/reference/index.rst +docs/html/reference/pip.rst +docs/html/reference/pip_check.rst +docs/html/reference/pip_config.rst +docs/html/reference/pip_download.rst +docs/html/reference/pip_freeze.rst +docs/html/reference/pip_hash.rst +docs/html/reference/pip_install.rst +docs/html/reference/pip_list.rst +docs/html/reference/pip_search.rst +docs/html/reference/pip_show.rst +docs/html/reference/pip_uninstall.rst +docs/html/reference/pip_wheel.rst +docs/man/index.rst +docs/man/commands/check.rst +docs/man/commands/config.rst +docs/man/commands/download.rst +docs/man/commands/freeze.rst +docs/man/commands/hash.rst +docs/man/commands/help.rst +docs/man/commands/install.rst +docs/man/commands/list.rst +docs/man/commands/search.rst +docs/man/commands/show.rst +docs/man/commands/uninstall.rst +docs/man/commands/wheel.rst +src/pip/__init__.py +src/pip/__main__.py +src/pip.egg-info/PKG-INFO +src/pip.egg-info/SOURCES.txt +src/pip.egg-info/dependency_links.txt +src/pip.egg-info/entry_points.txt +src/pip.egg-info/not-zip-safe +src/pip.egg-info/top_level.txt +src/pip/_internal/__init__.py +src/pip/_internal/build_env.py +src/pip/_internal/cache.py +src/pip/_internal/configuration.py +src/pip/_internal/download.py +src/pip/_internal/exceptions.py +src/pip/_internal/index.py +src/pip/_internal/locations.py +src/pip/_internal/pep425tags.py +src/pip/_internal/pyproject.py +src/pip/_internal/resolve.py +src/pip/_internal/wheel.py +src/pip/_internal/cli/__init__.py +src/pip/_internal/cli/autocompletion.py +src/pip/_internal/cli/base_command.py +src/pip/_internal/cli/cmdoptions.py +src/pip/_internal/cli/main_parser.py +src/pip/_internal/cli/parser.py +src/pip/_internal/cli/status_codes.py +src/pip/_internal/commands/__init__.py +src/pip/_internal/commands/check.py +src/pip/_internal/commands/completion.py +src/pip/_internal/commands/configuration.py +src/pip/_internal/commands/download.py +src/pip/_internal/commands/freeze.py +src/pip/_internal/commands/hash.py +src/pip/_internal/commands/help.py +src/pip/_internal/commands/install.py +src/pip/_internal/commands/list.py +src/pip/_internal/commands/search.py +src/pip/_internal/commands/show.py +src/pip/_internal/commands/uninstall.py +src/pip/_internal/commands/wheel.py +src/pip/_internal/models/__init__.py +src/pip/_internal/models/candidate.py +src/pip/_internal/models/format_control.py +src/pip/_internal/models/index.py +src/pip/_internal/models/link.py +src/pip/_internal/operations/__init__.py +src/pip/_internal/operations/check.py +src/pip/_internal/operations/freeze.py +src/pip/_internal/operations/prepare.py +src/pip/_internal/req/__init__.py +src/pip/_internal/req/constructors.py +src/pip/_internal/req/req_file.py +src/pip/_internal/req/req_install.py +src/pip/_internal/req/req_set.py +src/pip/_internal/req/req_tracker.py +src/pip/_internal/req/req_uninstall.py +src/pip/_internal/utils/__init__.py +src/pip/_internal/utils/appdirs.py +src/pip/_internal/utils/compat.py +src/pip/_internal/utils/deprecation.py +src/pip/_internal/utils/encoding.py +src/pip/_internal/utils/filesystem.py +src/pip/_internal/utils/glibc.py +src/pip/_internal/utils/hashes.py +src/pip/_internal/utils/logging.py +src/pip/_internal/utils/misc.py +src/pip/_internal/utils/models.py +src/pip/_internal/utils/outdated.py +src/pip/_internal/utils/packaging.py +src/pip/_internal/utils/setuptools_build.py +src/pip/_internal/utils/temp_dir.py +src/pip/_internal/utils/typing.py +src/pip/_internal/utils/ui.py +src/pip/_internal/vcs/__init__.py +src/pip/_internal/vcs/bazaar.py +src/pip/_internal/vcs/git.py +src/pip/_internal/vcs/mercurial.py +src/pip/_internal/vcs/subversion.py +src/pip/_vendor/README.rst +src/pip/_vendor/__init__.py +src/pip/_vendor/appdirs.LICENSE.txt +src/pip/_vendor/appdirs.py +src/pip/_vendor/distro.LICENSE +src/pip/_vendor/distro.py +src/pip/_vendor/ipaddress.LICENSE +src/pip/_vendor/ipaddress.py +src/pip/_vendor/pyparsing.LICENSE +src/pip/_vendor/pyparsing.py +src/pip/_vendor/retrying.LICENSE +src/pip/_vendor/retrying.py +src/pip/_vendor/six.LICENSE +src/pip/_vendor/six.py +src/pip/_vendor/vendor.txt +src/pip/_vendor/cachecontrol/LICENSE.txt +src/pip/_vendor/cachecontrol/__init__.py +src/pip/_vendor/cachecontrol/_cmd.py +src/pip/_vendor/cachecontrol/adapter.py +src/pip/_vendor/cachecontrol/cache.py +src/pip/_vendor/cachecontrol/compat.py +src/pip/_vendor/cachecontrol/controller.py +src/pip/_vendor/cachecontrol/filewrapper.py +src/pip/_vendor/cachecontrol/heuristics.py +src/pip/_vendor/cachecontrol/serialize.py +src/pip/_vendor/cachecontrol/wrapper.py +src/pip/_vendor/cachecontrol/caches/__init__.py +src/pip/_vendor/cachecontrol/caches/file_cache.py +src/pip/_vendor/cachecontrol/caches/redis_cache.py +src/pip/_vendor/certifi/LICENSE +src/pip/_vendor/certifi/__init__.py +src/pip/_vendor/certifi/__main__.py +src/pip/_vendor/certifi/cacert.pem +src/pip/_vendor/certifi/core.py +src/pip/_vendor/chardet/LICENSE +src/pip/_vendor/chardet/__init__.py +src/pip/_vendor/chardet/big5freq.py +src/pip/_vendor/chardet/big5prober.py +src/pip/_vendor/chardet/chardistribution.py +src/pip/_vendor/chardet/charsetgroupprober.py +src/pip/_vendor/chardet/charsetprober.py +src/pip/_vendor/chardet/codingstatemachine.py +src/pip/_vendor/chardet/compat.py +src/pip/_vendor/chardet/cp949prober.py +src/pip/_vendor/chardet/enums.py +src/pip/_vendor/chardet/escprober.py +src/pip/_vendor/chardet/escsm.py +src/pip/_vendor/chardet/eucjpprober.py +src/pip/_vendor/chardet/euckrfreq.py +src/pip/_vendor/chardet/euckrprober.py +src/pip/_vendor/chardet/euctwfreq.py +src/pip/_vendor/chardet/euctwprober.py +src/pip/_vendor/chardet/gb2312freq.py +src/pip/_vendor/chardet/gb2312prober.py +src/pip/_vendor/chardet/hebrewprober.py +src/pip/_vendor/chardet/jisfreq.py +src/pip/_vendor/chardet/jpcntx.py +src/pip/_vendor/chardet/langbulgarianmodel.py +src/pip/_vendor/chardet/langcyrillicmodel.py +src/pip/_vendor/chardet/langgreekmodel.py +src/pip/_vendor/chardet/langhebrewmodel.py +src/pip/_vendor/chardet/langhungarianmodel.py +src/pip/_vendor/chardet/langthaimodel.py +src/pip/_vendor/chardet/langturkishmodel.py +src/pip/_vendor/chardet/latin1prober.py +src/pip/_vendor/chardet/mbcharsetprober.py +src/pip/_vendor/chardet/mbcsgroupprober.py +src/pip/_vendor/chardet/mbcssm.py +src/pip/_vendor/chardet/sbcharsetprober.py +src/pip/_vendor/chardet/sbcsgroupprober.py +src/pip/_vendor/chardet/sjisprober.py +src/pip/_vendor/chardet/universaldetector.py +src/pip/_vendor/chardet/utf8prober.py +src/pip/_vendor/chardet/version.py +src/pip/_vendor/chardet/cli/__init__.py +src/pip/_vendor/chardet/cli/chardetect.py +src/pip/_vendor/colorama/LICENSE.txt +src/pip/_vendor/colorama/__init__.py +src/pip/_vendor/colorama/ansi.py +src/pip/_vendor/colorama/ansitowin32.py +src/pip/_vendor/colorama/initialise.py +src/pip/_vendor/colorama/win32.py +src/pip/_vendor/colorama/winterm.py +src/pip/_vendor/distlib/LICENSE.txt +src/pip/_vendor/distlib/__init__.py +src/pip/_vendor/distlib/compat.py +src/pip/_vendor/distlib/database.py +src/pip/_vendor/distlib/index.py +src/pip/_vendor/distlib/locators.py +src/pip/_vendor/distlib/manifest.py +src/pip/_vendor/distlib/markers.py +src/pip/_vendor/distlib/metadata.py +src/pip/_vendor/distlib/resources.py +src/pip/_vendor/distlib/scripts.py +src/pip/_vendor/distlib/t32.exe +src/pip/_vendor/distlib/t64.exe +src/pip/_vendor/distlib/util.py +src/pip/_vendor/distlib/version.py +src/pip/_vendor/distlib/w32.exe +src/pip/_vendor/distlib/w64.exe +src/pip/_vendor/distlib/wheel.py +src/pip/_vendor/distlib/_backport/__init__.py +src/pip/_vendor/distlib/_backport/misc.py +src/pip/_vendor/distlib/_backport/shutil.py +src/pip/_vendor/distlib/_backport/sysconfig.cfg +src/pip/_vendor/distlib/_backport/sysconfig.py +src/pip/_vendor/distlib/_backport/tarfile.py +src/pip/_vendor/html5lib/LICENSE +src/pip/_vendor/html5lib/__init__.py +src/pip/_vendor/html5lib/_ihatexml.py +src/pip/_vendor/html5lib/_inputstream.py +src/pip/_vendor/html5lib/_tokenizer.py +src/pip/_vendor/html5lib/_utils.py +src/pip/_vendor/html5lib/constants.py +src/pip/_vendor/html5lib/html5parser.py +src/pip/_vendor/html5lib/serializer.py +src/pip/_vendor/html5lib/_trie/__init__.py +src/pip/_vendor/html5lib/_trie/_base.py +src/pip/_vendor/html5lib/_trie/datrie.py +src/pip/_vendor/html5lib/_trie/py.py +src/pip/_vendor/html5lib/filters/__init__.py +src/pip/_vendor/html5lib/filters/alphabeticalattributes.py +src/pip/_vendor/html5lib/filters/base.py +src/pip/_vendor/html5lib/filters/inject_meta_charset.py +src/pip/_vendor/html5lib/filters/lint.py +src/pip/_vendor/html5lib/filters/optionaltags.py +src/pip/_vendor/html5lib/filters/sanitizer.py +src/pip/_vendor/html5lib/filters/whitespace.py +src/pip/_vendor/html5lib/treeadapters/__init__.py +src/pip/_vendor/html5lib/treeadapters/genshi.py +src/pip/_vendor/html5lib/treeadapters/sax.py +src/pip/_vendor/html5lib/treebuilders/__init__.py +src/pip/_vendor/html5lib/treebuilders/base.py +src/pip/_vendor/html5lib/treebuilders/dom.py +src/pip/_vendor/html5lib/treebuilders/etree.py +src/pip/_vendor/html5lib/treebuilders/etree_lxml.py +src/pip/_vendor/html5lib/treewalkers/__init__.py +src/pip/_vendor/html5lib/treewalkers/base.py +src/pip/_vendor/html5lib/treewalkers/dom.py +src/pip/_vendor/html5lib/treewalkers/etree.py +src/pip/_vendor/html5lib/treewalkers/etree_lxml.py +src/pip/_vendor/html5lib/treewalkers/genshi.py +src/pip/_vendor/idna/LICENSE.rst +src/pip/_vendor/idna/__init__.py +src/pip/_vendor/idna/codec.py +src/pip/_vendor/idna/compat.py +src/pip/_vendor/idna/core.py +src/pip/_vendor/idna/idnadata.py +src/pip/_vendor/idna/intranges.py +src/pip/_vendor/idna/package_data.py +src/pip/_vendor/idna/uts46data.py +src/pip/_vendor/lockfile/LICENSE +src/pip/_vendor/lockfile/__init__.py +src/pip/_vendor/lockfile/linklockfile.py +src/pip/_vendor/lockfile/mkdirlockfile.py +src/pip/_vendor/lockfile/pidlockfile.py +src/pip/_vendor/lockfile/sqlitelockfile.py +src/pip/_vendor/lockfile/symlinklockfile.py +src/pip/_vendor/msgpack/COPYING +src/pip/_vendor/msgpack/__init__.py +src/pip/_vendor/msgpack/_version.py +src/pip/_vendor/msgpack/exceptions.py +src/pip/_vendor/msgpack/fallback.py +src/pip/_vendor/packaging/LICENSE +src/pip/_vendor/packaging/LICENSE.APACHE +src/pip/_vendor/packaging/LICENSE.BSD +src/pip/_vendor/packaging/__about__.py +src/pip/_vendor/packaging/__init__.py +src/pip/_vendor/packaging/_compat.py +src/pip/_vendor/packaging/_structures.py +src/pip/_vendor/packaging/markers.py +src/pip/_vendor/packaging/requirements.py +src/pip/_vendor/packaging/specifiers.py +src/pip/_vendor/packaging/utils.py +src/pip/_vendor/packaging/version.py +src/pip/_vendor/pep517/LICENSE +src/pip/_vendor/pep517/__init__.py +src/pip/_vendor/pep517/_in_process.py +src/pip/_vendor/pep517/build.py +src/pip/_vendor/pep517/check.py +src/pip/_vendor/pep517/colorlog.py +src/pip/_vendor/pep517/compat.py +src/pip/_vendor/pep517/envbuild.py +src/pip/_vendor/pep517/wrappers.py +src/pip/_vendor/pkg_resources/LICENSE +src/pip/_vendor/pkg_resources/__init__.py +src/pip/_vendor/pkg_resources/py31compat.py +src/pip/_vendor/progress/LICENSE +src/pip/_vendor/progress/__init__.py +src/pip/_vendor/progress/bar.py +src/pip/_vendor/progress/counter.py +src/pip/_vendor/progress/helpers.py +src/pip/_vendor/progress/spinner.py +src/pip/_vendor/pytoml/LICENSE +src/pip/_vendor/pytoml/__init__.py +src/pip/_vendor/pytoml/core.py +src/pip/_vendor/pytoml/parser.py +src/pip/_vendor/pytoml/test.py +src/pip/_vendor/pytoml/utils.py +src/pip/_vendor/pytoml/writer.py +src/pip/_vendor/requests/LICENSE +src/pip/_vendor/requests/__init__.py +src/pip/_vendor/requests/__version__.py +src/pip/_vendor/requests/_internal_utils.py +src/pip/_vendor/requests/adapters.py +src/pip/_vendor/requests/api.py +src/pip/_vendor/requests/auth.py +src/pip/_vendor/requests/certs.py +src/pip/_vendor/requests/compat.py +src/pip/_vendor/requests/cookies.py +src/pip/_vendor/requests/exceptions.py +src/pip/_vendor/requests/help.py +src/pip/_vendor/requests/hooks.py +src/pip/_vendor/requests/models.py +src/pip/_vendor/requests/packages.py +src/pip/_vendor/requests/sessions.py +src/pip/_vendor/requests/status_codes.py +src/pip/_vendor/requests/structures.py +src/pip/_vendor/requests/utils.py +src/pip/_vendor/urllib3/LICENSE.txt +src/pip/_vendor/urllib3/__init__.py +src/pip/_vendor/urllib3/_collections.py +src/pip/_vendor/urllib3/connection.py +src/pip/_vendor/urllib3/connectionpool.py +src/pip/_vendor/urllib3/exceptions.py +src/pip/_vendor/urllib3/fields.py +src/pip/_vendor/urllib3/filepost.py +src/pip/_vendor/urllib3/poolmanager.py +src/pip/_vendor/urllib3/request.py +src/pip/_vendor/urllib3/response.py +src/pip/_vendor/urllib3/contrib/__init__.py +src/pip/_vendor/urllib3/contrib/_appengine_environ.py +src/pip/_vendor/urllib3/contrib/appengine.py +src/pip/_vendor/urllib3/contrib/ntlmpool.py +src/pip/_vendor/urllib3/contrib/pyopenssl.py +src/pip/_vendor/urllib3/contrib/securetransport.py +src/pip/_vendor/urllib3/contrib/socks.py +src/pip/_vendor/urllib3/contrib/_securetransport/__init__.py +src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py +src/pip/_vendor/urllib3/contrib/_securetransport/low_level.py +src/pip/_vendor/urllib3/packages/__init__.py +src/pip/_vendor/urllib3/packages/six.py +src/pip/_vendor/urllib3/packages/backports/__init__.py +src/pip/_vendor/urllib3/packages/backports/makefile.py +src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py +src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py +src/pip/_vendor/urllib3/util/__init__.py +src/pip/_vendor/urllib3/util/connection.py +src/pip/_vendor/urllib3/util/queue.py +src/pip/_vendor/urllib3/util/request.py +src/pip/_vendor/urllib3/util/response.py +src/pip/_vendor/urllib3/util/retry.py +src/pip/_vendor/urllib3/util/ssl_.py +src/pip/_vendor/urllib3/util/timeout.py +src/pip/_vendor/urllib3/util/url.py +src/pip/_vendor/urllib3/util/wait.py +src/pip/_vendor/webencodings/LICENSE +src/pip/_vendor/webencodings/__init__.py +src/pip/_vendor/webencodings/labels.py +src/pip/_vendor/webencodings/mklabels.py +src/pip/_vendor/webencodings/tests.py +src/pip/_vendor/webencodings/x_user_defined.py \ No newline at end of file diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/dependency_links.txt b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/entry_points.txt b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/entry_points.txt new file mode 100644 index 0000000..f5809cb --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +pip = pip._internal:main +pip3 = pip._internal:main +pip3.7 = pip._internal:main + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/not-zip-safe b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/not-zip-safe @@ -0,0 +1 @@ + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/top_level.txt b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/top_level.txt new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/EGG-INFO/top_level.txt @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__init__.py new file mode 100644 index 0000000..f48c1ca --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__init__.py @@ -0,0 +1 @@ +__version__ = "19.0.3" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__main__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__main__.py new file mode 100644 index 0000000..0c223f8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/__main__.py @@ -0,0 +1,19 @@ +from __future__ import absolute_import + +import os +import sys + +# If we are running from a wheel, add the wheel to sys.path +# This allows the usage python pip-*.whl/pip install pip-*.whl +if __package__ == '': + # __file__ is pip-*.whl/pip/__main__.py + # first dirname call strips of '/__main__.py', second strips off '/pip' + # Resulting path is the name of the wheel itself + # Add that to sys.path so we can import pip + path = os.path.dirname(os.path.dirname(__file__)) + sys.path.insert(0, path) + +from pip._internal import main as _main # isort:skip # noqa + +if __name__ == '__main__': + sys.exit(_main()) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/__init__.py new file mode 100644 index 0000000..276124d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/__init__.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +from __future__ import absolute_import + +import locale +import logging +import os +import warnings + +import sys + +# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks, +# but if invoked (i.e. imported), it will issue a warning to stderr if socks +# isn't available. requests unconditionally imports urllib3's socks contrib +# module, triggering this warning. The warning breaks DEP-8 tests (because of +# the stderr output) and is just plain annoying in normal usage. I don't want +# to add socks as yet another dependency for pip, nor do I want to allow-stder +# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to +# be done before the import of pip.vcs. +from pip._vendor.urllib3.exceptions import DependencyWarning +warnings.filterwarnings("ignore", category=DependencyWarning) # noqa + +# We want to inject the use of SecureTransport as early as possible so that any +# references or sessions or what have you are ensured to have it, however we +# only want to do this in the case that we're running on macOS and the linked +# OpenSSL is too old to handle TLSv1.2 +try: + import ssl +except ImportError: + pass +else: + # Checks for OpenSSL 1.0.1 on MacOS + if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f: + try: + from pip._vendor.urllib3.contrib import securetransport + except (ImportError, OSError): + pass + else: + securetransport.inject_into_urllib3() + +from pip._internal.cli.autocompletion import autocomplete +from pip._internal.cli.main_parser import parse_command +from pip._internal.commands import commands_dict +from pip._internal.exceptions import PipError +from pip._internal.utils import deprecation +from pip._internal.vcs import git, mercurial, subversion, bazaar # noqa +from pip._vendor.urllib3.exceptions import InsecureRequestWarning + +logger = logging.getLogger(__name__) + +# Hide the InsecureRequestWarning from urllib3 +warnings.filterwarnings("ignore", category=InsecureRequestWarning) + + +def main(args=None): + if args is None: + args = sys.argv[1:] + + # Configure our deprecation warnings to be sent through loggers + deprecation.install_warning_logger() + + autocomplete() + + try: + cmd_name, cmd_args = parse_command(args) + except PipError as exc: + sys.stderr.write("ERROR: %s" % exc) + sys.stderr.write(os.linesep) + sys.exit(1) + + # Needed for locale.getpreferredencoding(False) to work + # in pip._internal.utils.encoding.auto_decode + try: + locale.setlocale(locale.LC_ALL, '') + except locale.Error as e: + # setlocale can apparently crash if locale are uninitialized + logger.debug("Ignoring error %s when setting locale", e) + command = commands_dict[cmd_name](isolated=("--isolated" in cmd_args)) + return command.main(cmd_args) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/build_env.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/build_env.py new file mode 100644 index 0000000..d744cc7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/build_env.py @@ -0,0 +1,215 @@ +"""Build Environment used for isolation during sdist building +""" + +import logging +import os +import sys +import textwrap +from collections import OrderedDict +from distutils.sysconfig import get_python_lib +from sysconfig import get_paths + +from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet + +from pip import __file__ as pip_location +from pip._internal.utils.misc import call_subprocess +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.utils.ui import open_spinner + +if MYPY_CHECK_RUNNING: + from typing import Tuple, Set, Iterable, Optional, List # noqa: F401 + from pip._internal.index import PackageFinder # noqa: F401 + +logger = logging.getLogger(__name__) + + +class _Prefix: + + def __init__(self, path): + # type: (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'] + # Note: prefer distutils' sysconfig to get the + # library paths so PyPy is correctly supported. + purelib = get_python_lib(plat_specific=False, prefix=path) + platlib = get_python_lib(plat_specific=True, prefix=path) + if purelib == platlib: + self.lib_dirs = [purelib] + else: + self.lib_dirs = [purelib, platlib] + + +class BuildEnvironment(object): + """Creates and manages an isolated environment to install build deps + """ + + def __init__(self): + # type: () -> None + self._temp_dir = TempDirectory(kind="build-env") + self._temp_dir.create() + + self._prefixes = OrderedDict(( + (name, _Prefix(os.path.join(self._temp_dir.path, name))) + for name in ('normal', 'overlay') + )) + + self._bin_dirs = [] # type: List[str] + self._lib_dirs = [] # type: List[str] + for prefix in reversed(list(self._prefixes.values())): + self._bin_dirs.append(prefix.bin_dir) + self._lib_dirs.extend(prefix.lib_dirs) + + # Customize site to: + # - ensure .pth files are honored + # - prevent access to system site packages + system_sites = { + os.path.normcase(site) for site in ( + get_python_lib(plat_specific=False), + get_python_lib(plat_specific=True), + ) + } + self._site_dir = os.path.join(self._temp_dir.path, 'site') + if not os.path.exists(self._site_dir): + os.mkdir(self._site_dir) + with open(os.path.join(self._site_dir, 'sitecustomize.py'), 'w') as fp: + fp.write(textwrap.dedent( + ''' + import os, site, sys + + # First, drop system-sites related paths. + original_sys_path = sys.path[:] + known_paths = set() + for path in {system_sites!r}: + site.addsitedir(path, known_paths=known_paths) + system_paths = set( + os.path.normcase(path) + for path in sys.path[len(original_sys_path):] + ) + original_sys_path = [ + path for path in original_sys_path + if os.path.normcase(path) not in system_paths + ] + sys.path = original_sys_path + + # Second, add lib directories. + # ensuring .pth file are processed. + for path in {lib_dirs!r}: + assert not path in sys.path + site.addsitedir(path) + ''' + ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)) + + def __enter__(self): + self._save_env = { + name: os.environ.get(name, None) + for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH') + } + + path = self._bin_dirs[:] + old_path = self._save_env['PATH'] + if old_path: + path.extend(old_path.split(os.pathsep)) + + pythonpath = [self._site_dir] + + os.environ.update({ + 'PATH': os.pathsep.join(path), + 'PYTHONNOUSERSITE': '1', + 'PYTHONPATH': os.pathsep.join(pythonpath), + }) + + def __exit__(self, exc_type, exc_val, exc_tb): + for varname, old_value in self._save_env.items(): + if old_value is None: + os.environ.pop(varname, None) + else: + os.environ[varname] = old_value + + def cleanup(self): + # type: () -> None + self._temp_dir.cleanup() + + def check_requirements(self, reqs): + # type: (Iterable[str]) -> Tuple[Set[Tuple[str, str]], Set[str]] + """Return 2 sets: + - conflicting requirements: set of (installed, wanted) reqs tuples + - missing requirements: set of reqs + """ + missing = set() + conflicting = set() + if reqs: + ws = WorkingSet(self._lib_dirs) + for req in reqs: + try: + if ws.find(Requirement.parse(req)) is None: + missing.add(req) + except VersionConflict as e: + conflicting.add((str(e.args[0].as_requirement()), + str(e.args[1]))) + return conflicting, missing + + def install_requirements( + self, + finder, # type: PackageFinder + requirements, # type: Iterable[str] + prefix_as_string, # type: str + message # type: Optional[str] + ): + # type: (...) -> None + prefix = self._prefixes[prefix_as_string] + assert not prefix.setup + prefix.setup = True + if not requirements: + return + args = [ + sys.executable, os.path.dirname(pip_location), 'install', + '--ignore-installed', '--no-user', '--prefix', prefix.path, + '--no-warn-script-location', + ] # type: List[str] + if logger.getEffectiveLevel() <= logging.DEBUG: + args.append('-v') + for format_control in ('no_binary', 'only_binary'): + formats = getattr(finder.format_control, format_control) + args.extend(('--' + format_control.replace('_', '-'), + ','.join(sorted(formats or {':none:'})))) + if finder.index_urls: + args.extend(['-i', finder.index_urls[0]]) + for extra_index in finder.index_urls[1:]: + args.extend(['--extra-index-url', extra_index]) + else: + args.append('--no-index') + for link in finder.find_links: + args.extend(['--find-links', link]) + for _, host, _ in finder.secure_origins: + args.extend(['--trusted-host', host]) + if finder.allow_all_prereleases: + args.append('--pre') + args.append('--') + args.extend(requirements) + with open_spinner(message) as spinner: + call_subprocess(args, show_stdout=False, spinner=spinner) + + +class NoOpBuildEnvironment(BuildEnvironment): + """A no-op drop-in replacement for BuildEnvironment + """ + + def __init__(self): + pass + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def cleanup(self): + pass + + def install_requirements(self, finder, requirements, prefix, message): + raise NotImplementedError() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cache.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cache.py new file mode 100644 index 0000000..eb295c4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cache.py @@ -0,0 +1,224 @@ +"""Cache Management +""" + +import errno +import hashlib +import logging +import os + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.download import path_to_url +from pip._internal.models.link import Link +from pip._internal.utils.compat import expanduser +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.wheel import InvalidWheelFilename, Wheel + +if MYPY_CHECK_RUNNING: + from typing import Optional, Set, List, Any # noqa: F401 + from pip._internal.index import FormatControl # noqa: F401 + +logger = logging.getLogger(__name__) + + +class Cache(object): + """An abstract class - provides cache directories for data from links + + + :param cache_dir: The root of the cache. + :param format_control: An object of FormatControl class to limit + binaries being read from the cache. + :param allowed_formats: which formats of files the cache should store. + ('binary' and 'source' are the only allowed values) + """ + + def __init__(self, cache_dir, format_control, allowed_formats): + # type: (str, FormatControl, Set[str]) -> None + super(Cache, self).__init__() + self.cache_dir = expanduser(cache_dir) if cache_dir else None + self.format_control = format_control + self.allowed_formats = allowed_formats + + _valid_formats = {"source", "binary"} + assert self.allowed_formats.union(_valid_formats) == _valid_formats + + def _get_cache_path_parts(self, link): + # type: (Link) -> List[str] + """Get parts of part that must be os.path.joined with cache_dir + """ + + # We want to generate an url to use as our cache key, we don't want to + # just re-use the URL because it might have other items in the fragment + # and we don't care about those. + key_parts = [link.url_without_fragment] + if link.hash_name is not None and link.hash is not None: + key_parts.append("=".join([link.hash_name, link.hash])) + key_url = "#".join(key_parts) + + # Encode our key url with sha224, we'll use this because it has similar + # security properties to sha256, but with a shorter total output (and + # thus less secure). However the differences don't make a lot of + # difference for our use case here. + hashed = hashlib.sha224(key_url.encode()).hexdigest() + + # We want to nest the directories some to prevent having a ton of top + # level directories where we might run out of sub directories on some + # FS. + parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]] + + return parts + + def _get_candidates(self, link, package_name): + # type: (Link, Optional[str]) -> List[Any] + can_not_cache = ( + not self.cache_dir or + not package_name or + not link + ) + if can_not_cache: + return [] + + canonical_name = canonicalize_name(package_name) + formats = self.format_control.get_allowed_formats( + canonical_name + ) + if not self.allowed_formats.intersection(formats): + return [] + + root = self.get_path_for_link(link) + try: + return os.listdir(root) + except OSError as err: + if err.errno in {errno.ENOENT, errno.ENOTDIR}: + return [] + raise + + def get_path_for_link(self, link): + # type: (Link) -> str + """Return a directory to store cached items in for link. + """ + raise NotImplementedError() + + def get(self, link, package_name): + # type: (Link, Optional[str]) -> Link + """Returns a link to a cached item if it exists, otherwise returns the + passed link. + """ + raise NotImplementedError() + + def _link_for_candidate(self, link, candidate): + # type: (Link, str) -> Link + root = self.get_path_for_link(link) + path = os.path.join(root, candidate) + + return Link(path_to_url(path)) + + def cleanup(self): + # type: () -> None + pass + + +class SimpleWheelCache(Cache): + """A cache of wheels for future installs. + """ + + def __init__(self, cache_dir, format_control): + # type: (str, FormatControl) -> None + super(SimpleWheelCache, self).__init__( + cache_dir, format_control, {"binary"} + ) + + def get_path_for_link(self, link): + # type: (Link) -> str + """Return a directory to store cached wheels for link + + Because there are M wheels for any one sdist, we provide a directory + to cache them in, and then consult that directory when looking up + cache hits. + + We only insert things into the cache if they have plausible version + numbers, so that we don't contaminate the cache with things that were + not unique. E.g. ./package might have dozens of installs done for it + and build a version of 0.0...and if we built and cached a wheel, we'd + end up using the same wheel even if the source has been edited. + + :param link: The link of the sdist for which this will cache wheels. + """ + parts = self._get_cache_path_parts(link) + + # Store wheels within the root cache_dir + return os.path.join(self.cache_dir, "wheels", *parts) + + def get(self, link, package_name): + # type: (Link, Optional[str]) -> Link + candidates = [] + + for wheel_name in self._get_candidates(link, package_name): + try: + wheel = Wheel(wheel_name) + except InvalidWheelFilename: + continue + if not wheel.supported(): + # Built for a different python/arch/etc + continue + candidates.append((wheel.support_index_min(), wheel_name)) + + if not candidates: + return link + + return self._link_for_candidate(link, min(candidates)[1]) + + +class EphemWheelCache(SimpleWheelCache): + """A SimpleWheelCache that creates it's own temporary cache directory + """ + + def __init__(self, format_control): + # type: (FormatControl) -> None + self._temp_dir = TempDirectory(kind="ephem-wheel-cache") + self._temp_dir.create() + + super(EphemWheelCache, self).__init__( + self._temp_dir.path, format_control + ) + + def cleanup(self): + # type: () -> None + self._temp_dir.cleanup() + + +class WheelCache(Cache): + """Wraps EphemWheelCache and SimpleWheelCache into a single Cache + + This Cache allows for gracefully degradation, using the ephem wheel cache + when a certain link is not found in the simple wheel cache first. + """ + + def __init__(self, cache_dir, format_control): + # type: (str, FormatControl) -> None + super(WheelCache, self).__init__( + cache_dir, format_control, {'binary'} + ) + self._wheel_cache = SimpleWheelCache(cache_dir, format_control) + self._ephem_cache = EphemWheelCache(format_control) + + def get_path_for_link(self, link): + # type: (Link) -> str + return self._wheel_cache.get_path_for_link(link) + + def get_ephem_path_for_link(self, link): + # type: (Link) -> str + return self._ephem_cache.get_path_for_link(link) + + def get(self, link, package_name): + # type: (Link, Optional[str]) -> Link + retval = self._wheel_cache.get(link, package_name) + if retval is link: + retval = self._ephem_cache.get(link, package_name) + return retval + + def cleanup(self): + # type: () -> None + self._wheel_cache.cleanup() + self._ephem_cache.cleanup() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/__init__.py new file mode 100644 index 0000000..e589bb9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/__init__.py @@ -0,0 +1,4 @@ +"""Subpackage containing all of pip's command line interface related code +""" + +# This file intentionally does not import submodules diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/autocompletion.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/autocompletion.py new file mode 100644 index 0000000..0a04199 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/autocompletion.py @@ -0,0 +1,152 @@ +"""Logic that powers autocompletion installed by ``pip completion``. +""" + +import optparse +import os +import sys + +from pip._internal.cli.main_parser import create_main_parser +from pip._internal.commands import commands_dict, get_summaries +from pip._internal.utils.misc import get_installed_distributions + + +def autocomplete(): + """Entry Point for completion of main and subcommand options. + """ + # Don't complete if user hasn't sourced bash_completion file. + if 'PIP_AUTO_COMPLETE' not in os.environ: + return + cwords = os.environ['COMP_WORDS'].split()[1:] + cword = int(os.environ['COMP_CWORD']) + try: + current = cwords[cword - 1] + except IndexError: + current = '' + + subcommands = [cmd for cmd, summary in get_summaries()] + options = [] + # subcommand + try: + subcommand_name = [w for w in cwords if w in subcommands][0] + except IndexError: + subcommand_name = None + + parser = create_main_parser() + # subcommand options + if subcommand_name: + # special case: 'help' subcommand has no options + if subcommand_name == 'help': + sys.exit(1) + # special case: list locally installed dists for show and uninstall + should_list_installed = ( + subcommand_name in ['show', 'uninstall'] and + not current.startswith('-') + ) + if should_list_installed: + installed = [] + lc = current.lower() + for dist in get_installed_distributions(local_only=True): + if dist.key.startswith(lc) and dist.key not in cwords[1:]: + installed.append(dist.key) + # if there are no dists installed, fall back to option completion + if installed: + for dist in installed: + print(dist) + sys.exit(1) + + subcommand = commands_dict[subcommand_name]() + + for opt in subcommand.parser.option_list_all: + if opt.help != optparse.SUPPRESS_HELP: + for opt_str in opt._long_opts + opt._short_opts: + options.append((opt_str, opt.nargs)) + + # filter out previously specified options from available options + prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]] + options = [(x, v) for (x, v) in options if x not in prev_opts] + # filter options by current input + options = [(k, v) for k, v in options if k.startswith(current)] + # get completion type given cwords and available subcommand options + completion_type = get_path_completion_type( + cwords, cword, subcommand.parser.option_list_all, + ) + # get completion files and directories if ``completion_type`` is + # ````, ```` or ```` + if completion_type: + options = auto_complete_paths(current, completion_type) + options = ((opt, 0) for opt in options) + for option in options: + opt_label = option[0] + # append '=' to options which require args + if option[1] and option[0][:2] == "--": + opt_label += '=' + print(opt_label) + else: + # show main parser options only when necessary + + opts = [i.option_list for i in parser.option_groups] + opts.append(parser.option_list) + opts = (o for it in opts for o in it) + if current.startswith('-'): + for opt in opts: + if opt.help != optparse.SUPPRESS_HELP: + subcommands += opt._long_opts + opt._short_opts + else: + # get completion type given cwords and all available options + completion_type = get_path_completion_type(cwords, cword, opts) + if completion_type: + subcommands = auto_complete_paths(current, completion_type) + + print(' '.join([x for x in subcommands if x.startswith(current)])) + sys.exit(1) + + +def get_path_completion_type(cwords, cword, opts): + """Get the type of path completion (``file``, ``dir``, ``path`` or None) + + :param cwords: same as the environmental variable ``COMP_WORDS`` + :param cword: same as the environmental variable ``COMP_CWORD`` + :param opts: The available options to check + :return: path completion type (``file``, ``dir``, ``path`` or None) + """ + if cword < 2 or not cwords[cword - 2].startswith('-'): + return + for opt in opts: + if opt.help == optparse.SUPPRESS_HELP: + continue + for o in str(opt).split('/'): + if cwords[cword - 2].split('=')[0] == o: + if not opt.metavar or any( + x in ('path', 'file', 'dir') + for x in opt.metavar.split('/')): + return opt.metavar + + +def auto_complete_paths(current, completion_type): + """If ``completion_type`` is ``file`` or ``path``, list all regular files + and directories starting with ``current``; otherwise only list directories + starting with ``current``. + + :param current: The word to be completed + :param completion_type: path completion type(`file`, `path` or `dir`)i + :return: A generator of regular files and/or directories + """ + directory, filename = os.path.split(current) + current_path = os.path.abspath(directory) + # Don't complete paths if they can't be accessed + if not os.access(current_path, os.R_OK): + return + filename = os.path.normcase(filename) + # list all files that start with ``filename`` + file_list = (x for x in os.listdir(current_path) + if os.path.normcase(x).startswith(filename)) + for f in file_list: + opt = os.path.join(current_path, f) + comp_file = os.path.normcase(os.path.join(directory, f)) + # complete regular files when there is not ```` after option + # complete directories when there is ````, ```` or + # ````after option + if completion_type != 'dir' and os.path.isfile(opt): + yield comp_file + elif os.path.isdir(opt): + yield os.path.join(comp_file, '') diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/base_command.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/base_command.py new file mode 100644 index 0000000..3ceea49 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/base_command.py @@ -0,0 +1,341 @@ +"""Base Command class, and related routines""" +from __future__ import absolute_import, print_function + +import logging +import logging.config +import optparse +import os +import platform +import sys +import traceback + +from pip._internal.cli import cmdoptions +from pip._internal.cli.parser import ( + ConfigOptionParser, UpdatingDefaultsHelpFormatter, +) +from pip._internal.cli.status_codes import ( + ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR, + VIRTUALENV_NOT_FOUND, +) +from pip._internal.download import PipSession +from pip._internal.exceptions import ( + BadCommand, CommandError, InstallationError, PreviousBuildDirError, + UninstallationError, +) +from pip._internal.index import PackageFinder +from pip._internal.locations import running_under_virtualenv +from pip._internal.req.constructors import ( + install_req_from_editable, install_req_from_line, +) +from pip._internal.req.req_file import parse_requirements +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging +from pip._internal.utils.misc import ( + get_prog, normalize_path, redact_password_from_url, +) +from pip._internal.utils.outdated import pip_version_check +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional, List, Tuple, Any # noqa: F401 + from optparse import Values # noqa: F401 + from pip._internal.cache import WheelCache # noqa: F401 + from pip._internal.req.req_set import RequirementSet # noqa: F401 + +__all__ = ['Command'] + +logger = logging.getLogger(__name__) + + +class Command(object): + name = None # type: Optional[str] + usage = None # type: Optional[str] + hidden = False # type: bool + ignore_require_venv = False # type: bool + + def __init__(self, isolated=False): + # type: (bool) -> None + parser_kw = { + 'usage': self.usage, + 'prog': '%s %s' % (get_prog(), self.name), + 'formatter': UpdatingDefaultsHelpFormatter(), + 'add_help_option': False, + 'name': self.name, + 'description': self.__doc__, + 'isolated': isolated, + } + + self.parser = ConfigOptionParser(**parser_kw) + + # Commands should add options to this option group + optgroup_name = '%s Options' % self.name.capitalize() + self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name) + + # Add the general options + gen_opts = cmdoptions.make_option_group( + cmdoptions.general_group, + self.parser, + ) + self.parser.add_option_group(gen_opts) + + def run(self, options, args): + # type: (Values, List[Any]) -> Any + raise NotImplementedError + + def _build_session(self, options, retries=None, timeout=None): + # type: (Values, Optional[int], Optional[int]) -> PipSession + session = PipSession( + cache=( + normalize_path(os.path.join(options.cache_dir, "http")) + if options.cache_dir else None + ), + retries=retries if retries is not None else options.retries, + insecure_hosts=options.trusted_hosts, + ) + + # Handle custom ca-bundles from the user + if options.cert: + session.verify = options.cert + + # Handle SSL client certificate + if options.client_cert: + session.cert = options.client_cert + + # Handle timeouts + if options.timeout or timeout: + session.timeout = ( + timeout if timeout is not None else options.timeout + ) + + # Handle configured proxies + if options.proxy: + session.proxies = { + "http": options.proxy, + "https": options.proxy, + } + + # Determine if we can prompt the user for authentication or not + session.auth.prompting = not options.no_input + + return session + + def parse_args(self, args): + # type: (List[str]) -> Tuple + # factored out for testability + return self.parser.parse_args(args) + + def main(self, args): + # type: (List[str]) -> int + options, args = self.parse_args(args) + + # Set verbosity so that it can be used elsewhere. + self.verbosity = options.verbose - options.quiet + + level_number = setup_logging( + verbosity=self.verbosity, + no_color=options.no_color, + user_log_file=options.log, + ) + + if sys.version_info[:2] == (3, 4): + deprecated( + "Python 3.4 support has been deprecated. pip 19.1 will be the " + "last one supporting it. Please upgrade your Python as Python " + "3.4 won't be maintained after March 2019 (cf PEP 429).", + replacement=None, + gone_in='19.2', + ) + elif sys.version_info[:2] == (2, 7): + message = ( + "A future version of pip will drop support for Python 2.7." + ) + if platform.python_implementation() == "CPython": + message = ( + "Python 2.7 will reach the end of its life on January " + "1st, 2020. Please upgrade your Python as Python 2.7 " + "won't be maintained after that date. " + ) + message + deprecated(message, replacement=None, gone_in=None) + + # TODO: Try to get these passing down from the command? + # without resorting to os.environ to hold these. + # This also affects isolated builds and it should. + + if options.no_input: + os.environ['PIP_NO_INPUT'] = '1' + + if options.exists_action: + os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) + + if options.require_venv and not self.ignore_require_venv: + # If a venv is required check if it can really be found + if not running_under_virtualenv(): + logger.critical( + 'Could not find an activated virtualenv (required).' + ) + sys.exit(VIRTUALENV_NOT_FOUND) + + try: + status = self.run(options, args) + # FIXME: all commands should return an exit status + # and when it is done, isinstance is not needed anymore + if isinstance(status, int): + return status + except PreviousBuildDirError as exc: + logger.critical(str(exc)) + logger.debug('Exception information:', exc_info=True) + + return PREVIOUS_BUILD_DIR_ERROR + except (InstallationError, UninstallationError, BadCommand) as exc: + logger.critical(str(exc)) + logger.debug('Exception information:', exc_info=True) + + return ERROR + except CommandError as exc: + logger.critical('ERROR: %s', exc) + logger.debug('Exception information:', exc_info=True) + + return ERROR + except BrokenStdoutLoggingError: + # Bypass our logger and write any remaining messages to stderr + # because stdout no longer works. + print('ERROR: Pipe to stdout was broken', file=sys.stderr) + if level_number <= logging.DEBUG: + traceback.print_exc(file=sys.stderr) + + return ERROR + except KeyboardInterrupt: + logger.critical('Operation cancelled by user') + logger.debug('Exception information:', exc_info=True) + + return ERROR + except BaseException: + logger.critical('Exception:', exc_info=True) + + return UNKNOWN_ERROR + finally: + allow_version_check = ( + # Does this command have the index_group options? + hasattr(options, "no_index") and + # Is this command allowed to perform this check? + not (options.disable_pip_version_check or options.no_index) + ) + # Check if we're using the latest version of pip available + if allow_version_check: + session = self._build_session( + options, + retries=0, + timeout=min(5, options.timeout) + ) + with session: + pip_version_check(session, options) + + # Shutdown the logging module + logging.shutdown() + + return SUCCESS + + +class RequirementCommand(Command): + + @staticmethod + def populate_requirement_set(requirement_set, # type: RequirementSet + args, # type: List[str] + options, # type: Values + finder, # type: PackageFinder + session, # type: PipSession + name, # type: str + wheel_cache # type: Optional[WheelCache] + ): + # type: (...) -> None + """ + Marshal cmd line args into a requirement set. + """ + # NOTE: As a side-effect, options.require_hashes and + # requirement_set.require_hashes may be updated + + for filename in options.constraints: + for req_to_add in parse_requirements( + filename, + constraint=True, finder=finder, options=options, + session=session, wheel_cache=wheel_cache): + req_to_add.is_direct = True + requirement_set.add_requirement(req_to_add) + + for req in args: + req_to_add = install_req_from_line( + req, None, isolated=options.isolated_mode, + use_pep517=options.use_pep517, + wheel_cache=wheel_cache + ) + req_to_add.is_direct = True + requirement_set.add_requirement(req_to_add) + + for req in options.editables: + req_to_add = install_req_from_editable( + req, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + wheel_cache=wheel_cache + ) + req_to_add.is_direct = True + requirement_set.add_requirement(req_to_add) + + for filename in options.requirements: + for req_to_add in parse_requirements( + filename, + finder=finder, options=options, session=session, + wheel_cache=wheel_cache, + use_pep517=options.use_pep517): + req_to_add.is_direct = True + requirement_set.add_requirement(req_to_add) + # If --require-hashes was a line in a requirements file, tell + # RequirementSet about it: + requirement_set.require_hashes = options.require_hashes + + if not (args or options.editables or options.requirements): + opts = {'name': name} + if options.find_links: + raise CommandError( + 'You must give at least one requirement to %(name)s ' + '(maybe you meant "pip %(name)s %(links)s"?)' % + dict(opts, links=' '.join(options.find_links))) + else: + raise CommandError( + 'You must give at least one requirement to %(name)s ' + '(see "pip help %(name)s")' % opts) + + def _build_package_finder( + self, + options, # type: Values + session, # type: PipSession + platform=None, # type: Optional[str] + python_versions=None, # type: Optional[List[str]] + abi=None, # type: Optional[str] + implementation=None # type: Optional[str] + ): + # type: (...) -> PackageFinder + """ + Create a package finder appropriate to this requirement command. + """ + index_urls = [options.index_url] + options.extra_index_urls + if options.no_index: + logger.debug( + 'Ignoring indexes: %s', + ','.join(redact_password_from_url(url) for url in index_urls), + ) + index_urls = [] + + return PackageFinder( + find_links=options.find_links, + format_control=options.format_control, + index_urls=index_urls, + trusted_hosts=options.trusted_hosts, + allow_all_prereleases=options.pre, + session=session, + platform=platform, + versions=python_versions, + abi=abi, + implementation=implementation, + prefer_binary=options.prefer_binary, + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/cmdoptions.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/cmdoptions.py new file mode 100644 index 0000000..5cf5ee9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/cmdoptions.py @@ -0,0 +1,809 @@ +""" +shared options and groups + +The principle here is to define options once, but *not* instantiate them +globally. One reason being that options with action='append' can carry state +between parses. pip parses general options twice internally, and shouldn't +pass on state. To be consistent, all options will follow this design. + +""" +from __future__ import absolute_import + +import textwrap +import warnings +from distutils.util import strtobool +from functools import partial +from optparse import SUPPRESS_HELP, Option, OptionGroup + +from pip._internal.exceptions import CommandError +from pip._internal.locations import USER_CACHE_DIR, src_prefix +from pip._internal.models.format_control import FormatControl +from pip._internal.models.index import PyPI +from pip._internal.utils.hashes import STRONG_HASHES +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.utils.ui import BAR_TYPES + +if MYPY_CHECK_RUNNING: + from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401 + from optparse import OptionParser, Values # noqa: F401 + from pip._internal.cli.parser import ConfigOptionParser # noqa: F401 + + +def raise_option_error(parser, option, msg): + """ + Raise an option parsing error using parser.error(). + + Args: + parser: an OptionParser instance. + option: an Option instance. + msg: the error text. + """ + msg = '{} error: {}'.format(option, msg) + msg = textwrap.fill(' '.join(msg.split())) + parser.error(msg) + + +def make_option_group(group, parser): + # type: (Dict[str, Any], ConfigOptionParser) -> OptionGroup + """ + Return an OptionGroup object + group -- assumed to be dict with 'name' and 'options' keys + parser -- an optparse Parser + """ + option_group = OptionGroup(parser, group['name']) + for option in group['options']: + option_group.add_option(option()) + return option_group + + +def check_install_build_global(options, check_options=None): + # type: (Values, Optional[Values]) -> 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): + 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-options ' + '/ --global-options / --install-options.', stacklevel=2, + ) + + +def check_dist_restriction(options, check_target=False): + # type: (Values, bool) -> None + """Function for determining if custom platform options are allowed. + + :param options: The OptionParser options. + :param check_target: Whether or not to check if --target is being used. + """ + dist_restriction_set = any([ + options.python_version, + options.platform, + options.abi, + options.implementation, + ]) + + binary_only = FormatControl(set(), {':all:'}) + sdist_dependencies_allowed = ( + options.format_control != binary_only and + not options.ignore_dependencies + ) + + # Installations or downloads using dist restrictions must not combine + # source distributions and dist-specific wheels, as they are not + # gauranteed to be locally compatible. + if dist_restriction_set and sdist_dependencies_allowed: + raise CommandError( + "When restricting platform and interpreter constraints using " + "--python-version, --platform, --abi, or --implementation, " + "either --no-deps must be set, or --only-binary=:all: must be " + "set and --no-binary must not be set (or must be set to " + ":none:)." + ) + + if check_target: + if dist_restriction_set and not options.target_dir: + raise CommandError( + "Can not use any platform or abi specific options unless " + "installing via '--target'" + ) + + +########### +# options # +########### + +help_ = partial( + Option, + '-h', '--help', + dest='help', + action='help', + help='Show help.', +) # type: Callable[..., Option] + +isolated_mode = partial( + Option, + "--isolated", + dest="isolated_mode", + action="store_true", + default=False, + help=( + "Run pip in an isolated mode, ignoring environment variables and user " + "configuration." + ), +) # type: Callable[..., Option] + +require_virtualenv = partial( + Option, + # Run only if inside a virtualenv, bail if not. + '--require-virtualenv', '--require-venv', + dest='require_venv', + action='store_true', + default=False, + help=SUPPRESS_HELP +) # type: Callable[..., Option] + +verbose = partial( + Option, + '-v', '--verbose', + dest='verbose', + action='count', + default=0, + help='Give more output. Option is additive, and can be used up to 3 times.' +) # type: Callable[..., Option] + +no_color = partial( + Option, + '--no-color', + dest='no_color', + action='store_true', + default=False, + help="Suppress colored output", +) # type: Callable[..., Option] + +version = partial( + Option, + '-V', '--version', + dest='version', + action='store_true', + help='Show version and exit.', +) # type: Callable[..., Option] + +quiet = partial( + Option, + '-q', '--quiet', + dest='quiet', + action='count', + default=0, + help=( + 'Give less output. Option is additive, and can be used up to 3' + ' times (corresponding to WARNING, ERROR, and CRITICAL logging' + ' levels).' + ), +) # type: Callable[..., Option] + +progress_bar = partial( + Option, + '--progress-bar', + dest='progress_bar', + type='choice', + choices=list(BAR_TYPES.keys()), + default='on', + help=( + 'Specify type of progress to be displayed [' + + '|'.join(BAR_TYPES.keys()) + '] (default: %default)' + ), +) # type: Callable[..., Option] + +log = partial( + Option, + "--log", "--log-file", "--local-log", + dest="log", + metavar="path", + help="Path to a verbose appending log." +) # type: Callable[..., Option] + +no_input = partial( + Option, + # Don't ask for input + '--no-input', + dest='no_input', + action='store_true', + default=False, + help=SUPPRESS_HELP +) # type: Callable[..., Option] + +proxy = partial( + Option, + '--proxy', + dest='proxy', + type='str', + default='', + help="Specify a proxy in the form [user:passwd@]proxy.server:port." +) # type: Callable[..., Option] + +retries = partial( + Option, + '--retries', + dest='retries', + type='int', + default=5, + help="Maximum number of retries each connection should attempt " + "(default %default times).", +) # type: Callable[..., Option] + +timeout = partial( + Option, + '--timeout', '--default-timeout', + metavar='sec', + dest='timeout', + type='float', + default=15, + help='Set the socket timeout (default %default seconds).', +) # type: Callable[..., Option] + +skip_requirements_regex = partial( + Option, + # A regex to be used to skip requirements + '--skip-requirements-regex', + dest='skip_requirements_regex', + type='str', + default='', + help=SUPPRESS_HELP, +) # type: Callable[..., Option] + + +def exists_action(): + # type: () -> Option + return Option( + # Option when path already exist + '--exists-action', + dest='exists_action', + type='choice', + choices=['s', 'i', 'w', 'b', 'a'], + default=[], + action='append', + metavar='action', + help="Default action when a path already exists: " + "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort).", + ) + + +cert = partial( + Option, + '--cert', + dest='cert', + type='str', + metavar='path', + help="Path to alternate CA bundle.", +) # type: Callable[..., Option] + +client_cert = partial( + Option, + '--client-cert', + dest='client_cert', + type='str', + default=None, + metavar='path', + help="Path to SSL client certificate, a single file containing the " + "private key and the certificate in PEM format.", +) # type: Callable[..., Option] + +index_url = partial( + Option, + '-i', '--index-url', '--pypi-url', + dest='index_url', + metavar='URL', + default=PyPI.simple_url, + help="Base URL of Python Package Index (default %default). " + "This should point to a repository compliant with PEP 503 " + "(the simple repository API) or a local directory laid out " + "in the same format.", +) # type: Callable[..., Option] + + +def extra_index_url(): + return Option( + '--extra-index-url', + dest='extra_index_urls', + metavar='URL', + action='append', + default=[], + help="Extra URLs of package indexes to use in addition to " + "--index-url. Should follow the same rules as " + "--index-url.", + ) + + +no_index = partial( + Option, + '--no-index', + dest='no_index', + action='store_true', + default=False, + help='Ignore package index (only looking at --find-links URLs instead).', +) # type: Callable[..., Option] + + +def find_links(): + # type: () -> Option + return Option( + '-f', '--find-links', + dest='find_links', + action='append', + default=[], + metavar='url', + help="If a url or path to an html file, then parse for links to " + "archives. If a local path or file:// url that's a directory, " + "then look for archives in the directory listing.", + ) + + +def trusted_host(): + # type: () -> Option + return Option( + "--trusted-host", + dest="trusted_hosts", + action="append", + metavar="HOSTNAME", + default=[], + help="Mark this host as trusted, even though it does not have valid " + "or any HTTPS.", + ) + + +def constraints(): + # type: () -> Option + return Option( + '-c', '--constraint', + dest='constraints', + action='append', + default=[], + metavar='file', + help='Constrain versions using the given constraints file. ' + 'This option can be used multiple times.' + ) + + +def requirements(): + # type: () -> Option + return Option( + '-r', '--requirement', + dest='requirements', + action='append', + default=[], + metavar='file', + help='Install from the given requirements file. ' + 'This option can be used multiple times.' + ) + + +def editable(): + # type: () -> Option + return Option( + '-e', '--editable', + dest='editables', + action='append', + default=[], + metavar='path/url', + help=('Install a project in editable mode (i.e. setuptools ' + '"develop mode") from a local project path or a VCS url.'), + ) + + +src = partial( + Option, + '--src', '--source', '--source-dir', '--source-directory', + dest='src_dir', + metavar='dir', + default=src_prefix, + help='Directory to check out editable projects into. ' + 'The default in a virtualenv is "/src". ' + 'The default for global installs is "/src".' +) # type: Callable[..., Option] + + +def _get_format_control(values, option): + # type: (Values, Option) -> Any + """Get a format_control object.""" + return getattr(values, option.dest) + + +def _handle_no_binary(option, opt_str, value, parser): + # type: (Option, str, str, OptionParser) -> None + existing = _get_format_control(parser.values, option) + FormatControl.handle_mutual_excludes( + value, existing.no_binary, existing.only_binary, + ) + + +def _handle_only_binary(option, opt_str, value, parser): + # type: (Option, str, str, OptionParser) -> None + existing = _get_format_control(parser.values, option) + FormatControl.handle_mutual_excludes( + value, existing.only_binary, existing.no_binary, + ) + + +def no_binary(): + # type: () -> Option + format_control = FormatControl(set(), set()) + return Option( + "--no-binary", dest="format_control", action="callback", + callback=_handle_no_binary, type="str", + default=format_control, + help="Do not use binary packages. Can be supplied multiple times, and " + "each time adds to the existing value. Accepts either :all: to " + "disable all binary packages, :none: to empty the set, or one or " + "more package names with commas between them. Note that some " + "packages are tricky to compile and may fail to install when " + "this option is used on them.", + ) + + +def only_binary(): + # type: () -> Option + format_control = FormatControl(set(), set()) + return Option( + "--only-binary", dest="format_control", action="callback", + callback=_handle_only_binary, type="str", + default=format_control, + help="Do not use source packages. Can be supplied multiple times, and " + "each time adds to the existing value. Accepts either :all: to " + "disable all source packages, :none: to empty the set, or one or " + "more package names with commas between them. Packages without " + "binary distributions will fail to install when this option is " + "used on them.", + ) + + +platform = partial( + Option, + '--platform', + dest='platform', + metavar='platform', + default=None, + help=("Only use wheels compatible with . " + "Defaults to the platform of the running system."), +) # type: Callable[..., Option] + + +python_version = partial( + Option, + '--python-version', + dest='python_version', + metavar='python_version', + default=None, + help=("Only use wheels compatible with Python " + "interpreter version . If not specified, then the " + "current system interpreter minor version is used. A major " + "version (e.g. '2') can be specified to match all " + "minor revs of that major version. A minor version " + "(e.g. '34') can also be specified."), +) # type: Callable[..., Option] + + +implementation = partial( + Option, + '--implementation', + dest='implementation', + metavar='implementation', + default=None, + help=("Only use wheels compatible with Python " + "implementation , e.g. 'pp', 'jy', 'cp', " + " or 'ip'. If not specified, then the current " + "interpreter implementation is used. Use 'py' to force " + "implementation-agnostic wheels."), +) # type: Callable[..., Option] + + +abi = partial( + Option, + '--abi', + dest='abi', + metavar='abi', + default=None, + help=("Only use wheels compatible with Python " + "abi , e.g. 'pypy_41'. If not specified, then the " + "current interpreter abi tag is used. Generally " + "you will need to specify --implementation, " + "--platform, and --python-version when using " + "this option."), +) # type: Callable[..., Option] + + +def prefer_binary(): + # type: () -> Option + return Option( + "--prefer-binary", + dest="prefer_binary", + action="store_true", + default=False, + help="Prefer older binary packages over newer source packages." + ) + + +cache_dir = partial( + Option, + "--cache-dir", + dest="cache_dir", + default=USER_CACHE_DIR, + metavar="dir", + help="Store the cache data in ." +) # type: Callable[..., Option] + + +def no_cache_dir_callback(option, opt, value, parser): + """ + Process a value provided for the --no-cache-dir option. + + This is an optparse.Option callback for the --no-cache-dir option. + """ + # The value argument will be None if --no-cache-dir is passed via the + # command-line, since the option doesn't accept arguments. However, + # the value can be non-None if the option is triggered e.g. by an + # environment variable, like PIP_NO_CACHE_DIR=true. + if value is not None: + # Then parse the string value to get argument error-checking. + try: + strtobool(value) + except ValueError as exc: + raise_option_error(parser, option=option, msg=str(exc)) + + # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool() + # converted to 0 (like "false" or "no") caused cache_dir to be disabled + # rather than enabled (logic would say the latter). Thus, we disable + # the cache directory not just on values that parse to True, but (for + # backwards compatibility reasons) also on values that parse to False. + # In other words, always set it to False if the option is provided in + # some (valid) form. + parser.values.cache_dir = False + + +no_cache = partial( + Option, + "--no-cache-dir", + dest="cache_dir", + action="callback", + callback=no_cache_dir_callback, + help="Disable the cache.", +) # type: Callable[..., Option] + +no_deps = partial( + Option, + '--no-deps', '--no-dependencies', + dest='ignore_dependencies', + action='store_true', + default=False, + help="Don't install package dependencies.", +) # type: Callable[..., Option] + +build_dir = partial( + Option, + '-b', '--build', '--build-dir', '--build-directory', + dest='build_dir', + metavar='dir', + help='Directory to unpack packages into and build in. Note that ' + 'an initial build still takes place in a temporary directory. ' + 'The location of temporary directories can be controlled by setting ' + 'the TMPDIR environment variable (TEMP on Windows) appropriately. ' + 'When passed, build directories are not cleaned in case of failures.' +) # type: Callable[..., Option] + +ignore_requires_python = partial( + Option, + '--ignore-requires-python', + dest='ignore_requires_python', + action='store_true', + help='Ignore the Requires-Python information.' +) # type: Callable[..., Option] + +no_build_isolation = partial( + Option, + '--no-build-isolation', + dest='build_isolation', + action='store_false', + default=True, + help='Disable isolation when building a modern source distribution. ' + 'Build dependencies specified by PEP 518 must be already installed ' + 'if this option is used.' +) # type: Callable[..., Option] + + +def no_use_pep517_callback(option, opt, value, parser): + """ + Process a value provided for the --no-use-pep517 option. + + This is an optparse.Option callback for the no_use_pep517 option. + """ + # Since --no-use-pep517 doesn't accept arguments, the value argument + # will be None if --no-use-pep517 is passed via the command-line. + # However, the value can be non-None if the option is triggered e.g. + # by an environment variable, for example "PIP_NO_USE_PEP517=true". + if value is not None: + msg = """A value was passed for --no-use-pep517, + probably using either the PIP_NO_USE_PEP517 environment variable + or the "no-use-pep517" config file option. Use an appropriate value + of the PIP_USE_PEP517 environment variable or the "use-pep517" + config file option instead. + """ + raise_option_error(parser, option=option, msg=msg) + + # Otherwise, --no-use-pep517 was passed via the command-line. + parser.values.use_pep517 = False + + +use_pep517 = partial( + Option, + '--use-pep517', + dest='use_pep517', + action='store_true', + default=None, + help='Use PEP 517 for building source distributions ' + '(use --no-use-pep517 to force legacy behaviour).' +) # type: Any + +no_use_pep517 = partial( + Option, + '--no-use-pep517', + dest='use_pep517', + action='callback', + callback=no_use_pep517_callback, + default=None, + help=SUPPRESS_HELP +) # type: Any + +install_options = 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.", +) # type: Callable[..., Option] + +global_options = partial( + Option, + '--global-option', + dest='global_options', + action='append', + metavar='options', + help="Extra global options to be supplied to the setup.py " + "call before the install command.", +) # type: Callable[..., Option] + +no_clean = partial( + Option, + '--no-clean', + action='store_true', + default=False, + help="Don't clean up build directories." +) # type: Callable[..., Option] + +pre = partial( + Option, + '--pre', + action='store_true', + default=False, + help="Include pre-release and development versions. By default, " + "pip only finds stable versions.", +) # type: Callable[..., Option] + +disable_pip_version_check = partial( + Option, + "--disable-pip-version-check", + dest="disable_pip_version_check", + action="store_true", + default=False, + help="Don't periodically check PyPI to determine whether a new version " + "of pip is available for download. Implied with --no-index.", +) # type: Callable[..., Option] + + +# Deprecated, Remove later +always_unzip = partial( + Option, + '-Z', '--always-unzip', + dest='always_unzip', + action='store_true', + help=SUPPRESS_HELP, +) # type: Callable[..., Option] + + +def _merge_hash(option, opt_str, value, parser): + # type: (Option, str, str, OptionParser) -> None + """Given a value spelled "algo:digest", append the digest to a list + pointed to in a dict by the algo name.""" + if not parser.values.hashes: + parser.values.hashes = {} # type: ignore + try: + algo, digest = value.split(':', 1) + except ValueError: + parser.error('Arguments to %s must be a hash name ' + 'followed by a value, like --hash=sha256:abcde...' % + opt_str) + if algo not in STRONG_HASHES: + parser.error('Allowed hash algorithms for %s are %s.' % + (opt_str, ', '.join(STRONG_HASHES))) + parser.values.hashes.setdefault(algo, []).append(digest) + + +hash = partial( + Option, + '--hash', + # Hash values eventually end up in InstallRequirement.hashes due to + # __dict__ copying in process_line(). + dest='hashes', + action='callback', + callback=_merge_hash, + type='string', + help="Verify that the package's archive matches this " + 'hash before installing. Example: --hash=sha256:abcdef...', +) # type: Callable[..., Option] + + +require_hashes = partial( + Option, + '--require-hashes', + dest='require_hashes', + action='store_true', + default=False, + help='Require a hash to check each requirement against, for ' + 'repeatable installs. This option is implied when any package in a ' + 'requirements file has a --hash option.', +) # type: Callable[..., Option] + + +########## +# groups # +########## + +general_group = { + 'name': 'General Options', + 'options': [ + help_, + isolated_mode, + require_virtualenv, + verbose, + version, + quiet, + log, + no_input, + proxy, + retries, + timeout, + skip_requirements_regex, + exists_action, + trusted_host, + cert, + client_cert, + cache_dir, + no_cache, + disable_pip_version_check, + no_color, + ] +} # type: Dict[str, Any] + +index_group = { + 'name': 'Package Index Options', + 'options': [ + index_url, + extra_index_url, + no_index, + find_links, + ] +} # type: Dict[str, Any] diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/main_parser.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/main_parser.py new file mode 100644 index 0000000..b17c749 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/main_parser.py @@ -0,0 +1,104 @@ +"""A single place for constructing and exposing the main parser +""" + +import os +import sys + +from pip import __version__ +from pip._internal.cli import cmdoptions +from pip._internal.cli.parser import ( + ConfigOptionParser, UpdatingDefaultsHelpFormatter, +) +from pip._internal.commands import ( + commands_dict, get_similar_commands, get_summaries, +) +from pip._internal.exceptions import CommandError +from pip._internal.utils.misc import get_prog +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Tuple, List # noqa: F401 + + +__all__ = ["create_main_parser", "parse_command"] + + +def create_main_parser(): + # type: () -> ConfigOptionParser + """Creates and returns the main parser for pip's CLI + """ + + parser_kw = { + 'usage': '\n%prog [options]', + 'add_help_option': False, + 'formatter': UpdatingDefaultsHelpFormatter(), + 'name': 'global', + 'prog': get_prog(), + } + + parser = ConfigOptionParser(**parser_kw) + parser.disable_interspersed_args() + + pip_pkg_dir = os.path.abspath(os.path.join( + os.path.dirname(__file__), "..", "..", + )) + parser.version = 'pip %s from %s (python %s)' % ( + __version__, pip_pkg_dir, sys.version[:3], + ) + + # add the general options + gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser) + parser.add_option_group(gen_opts) + + # so the help formatter knows + parser.main = True # type: ignore + + # create command listing for description + command_summaries = get_summaries() + description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries] + parser.description = '\n'.join(description) + + return parser + + +def parse_command(args): + # type: (List[str]) -> Tuple[str, List[str]] + parser = create_main_parser() + + # Note: parser calls disable_interspersed_args(), so the result of this + # call is to split the initial args into the general options before the + # subcommand and everything else. + # For example: + # args: ['--timeout=5', 'install', '--user', 'INITools'] + # general_options: ['--timeout==5'] + # args_else: ['install', '--user', 'INITools'] + general_options, args_else = parser.parse_args(args) + + # --version + if general_options.version: + sys.stdout.write(parser.version) # type: ignore + sys.stdout.write(os.linesep) + sys.exit() + + # pip || pip help -> print_help() + if not args_else or (args_else[0] == 'help' and len(args_else) == 1): + parser.print_help() + sys.exit() + + # the subcommand name + cmd_name = args_else[0] + + if cmd_name not in commands_dict: + guess = get_similar_commands(cmd_name) + + msg = ['unknown command "%s"' % cmd_name] + if guess: + msg.append('maybe you meant "%s"' % guess) + + raise CommandError(' - '.join(msg)) + + # all the args without the subcommand + cmd_args = args[:] + cmd_args.remove(cmd_name) + + return cmd_name, cmd_args diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/parser.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/parser.py new file mode 100644 index 0000000..e1eaac4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/parser.py @@ -0,0 +1,261 @@ +"""Base option parser setup""" +from __future__ import absolute_import + +import logging +import optparse +import sys +import textwrap +from distutils.util import strtobool + +from pip._vendor.six import string_types + +from pip._internal.cli.status_codes import UNKNOWN_ERROR +from pip._internal.configuration import Configuration, ConfigurationError +from pip._internal.utils.compat import get_terminal_size + +logger = logging.getLogger(__name__) + + +class PrettyHelpFormatter(optparse.IndentedHelpFormatter): + """A prettier/less verbose help formatter for optparse.""" + + def __init__(self, *args, **kwargs): + # help position must be aligned with __init__.parseopts.description + kwargs['max_help_position'] = 30 + kwargs['indent_increment'] = 1 + kwargs['width'] = get_terminal_size()[0] - 2 + optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs) + + def format_option_strings(self, option): + return self._format_option_strings(option, ' <%s>', ', ') + + def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '): + """ + Return a comma-separated list of option strings and metavars. + + :param option: tuple of (short opt, long opt), e.g: ('-f', '--format') + :param mvarfmt: metavar format string - evaluated as mvarfmt % metavar + :param optsep: separator + """ + opts = [] + + if option._short_opts: + opts.append(option._short_opts[0]) + if option._long_opts: + opts.append(option._long_opts[0]) + if len(opts) > 1: + opts.insert(1, optsep) + + if option.takes_value(): + metavar = option.metavar or option.dest.lower() + opts.append(mvarfmt % metavar.lower()) + + return ''.join(opts) + + def format_heading(self, heading): + if heading == 'Options': + return '' + return heading + ':\n' + + def format_usage(self, usage): + """ + Ensure there is only one newline between usage and the first heading + if there is no description. + """ + msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ") + return msg + + def format_description(self, description): + # leave full control over description to us + if description: + if hasattr(self.parser, 'main'): + label = 'Commands' + else: + label = 'Description' + # some doc strings have initial newlines, some don't + description = description.lstrip('\n') + # some doc strings have final newlines and spaces, some don't + description = description.rstrip() + # dedent, then reindent + description = self.indent_lines(textwrap.dedent(description), " ") + description = '%s:\n%s\n' % (label, description) + return description + else: + return '' + + def format_epilog(self, epilog): + # leave full control over epilog to us + if epilog: + return epilog + else: + return '' + + def indent_lines(self, text, indent): + new_lines = [indent + line for line in text.split('\n')] + return "\n".join(new_lines) + + +class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter): + """Custom help formatter for use in ConfigOptionParser. + + This is updates the defaults before expanding them, allowing + them to show up correctly in the help listing. + """ + + def expand_default(self, option): + if self.parser is not None: + self.parser._update_defaults(self.parser.defaults) + return optparse.IndentedHelpFormatter.expand_default(self, option) + + +class CustomOptionParser(optparse.OptionParser): + + def insert_option_group(self, idx, *args, **kwargs): + """Insert an OptionGroup at a given position.""" + group = self.add_option_group(*args, **kwargs) + + self.option_groups.pop() + self.option_groups.insert(idx, group) + + return group + + @property + def option_list_all(self): + """Get a list of all options, including those in option groups.""" + res = self.option_list[:] + for i in self.option_groups: + res.extend(i.option_list) + + return res + + +class ConfigOptionParser(CustomOptionParser): + """Custom option parser which updates its defaults by checking the + configuration files and environmental variables""" + + def __init__(self, *args, **kwargs): + self.name = kwargs.pop('name') + + isolated = kwargs.pop("isolated", False) + self.config = Configuration(isolated) + + assert self.name + optparse.OptionParser.__init__(self, *args, **kwargs) + + def check_default(self, option, key, val): + try: + return option.check_value(key, val) + except optparse.OptionValueError as exc: + print("An error occurred during configuration: %s" % exc) + sys.exit(3) + + def _get_ordered_configuration_items(self): + # Configuration gives keys in an unordered manner. Order them. + override_order = ["global", self.name, ":env:"] + + # Pool the options into different groups + section_items = {name: [] for name in override_order} + for section_key, val in self.config.items(): + # ignore empty values + if not val: + logger.debug( + "Ignoring configuration key '%s' as it's value is empty.", + section_key + ) + continue + + section, key = section_key.split(".", 1) + if section in override_order: + section_items[section].append((key, val)) + + # Yield each group in their override order + for section in override_order: + for key, val in section_items[section]: + yield key, val + + def _update_defaults(self, defaults): + """Updates the given defaults with values from the config files and + the environ. Does a little special handling for certain types of + options (lists).""" + + # Accumulate complex default state. + self.values = optparse.Values(self.defaults) + late_eval = set() + # Then set the options with those values + for key, val in self._get_ordered_configuration_items(): + # '--' because configuration supports only long names + option = self.get_option('--' + key) + + # Ignore options not present in this parser. E.g. non-globals put + # in [global] by users that want them to apply to all applicable + # commands. + if option is None: + continue + + if option.action in ('store_true', 'store_false', 'count'): + try: + val = strtobool(val) + except ValueError: + error_msg = invalid_config_error_message( + option.action, key, val + ) + self.error(error_msg) + + elif option.action == 'append': + val = val.split() + val = [self.check_default(option, key, v) for v in val] + elif option.action == 'callback': + late_eval.add(option.dest) + opt_str = option.get_opt_string() + val = option.convert_value(opt_str, val) + # From take_action + args = option.callback_args or () + kwargs = option.callback_kwargs or {} + option.callback(option, opt_str, val, self, *args, **kwargs) + else: + val = self.check_default(option, key, val) + + defaults[option.dest] = val + + for key in late_eval: + defaults[key] = getattr(self.values, key) + self.values = None + return defaults + + def get_default_values(self): + """Overriding to make updating the defaults after instantiation of + the option parser possible, _update_defaults() does the dirty work.""" + if not self.process_default_values: + # Old, pre-Optik 1.5 behaviour. + return optparse.Values(self.defaults) + + # Load the configuration, or error out in case of an error + try: + self.config.load() + except ConfigurationError as err: + self.exit(UNKNOWN_ERROR, str(err)) + + defaults = self._update_defaults(self.defaults.copy()) # ours + for option in self._get_all_options(): + default = defaults.get(option.dest) + if isinstance(default, string_types): + opt_str = option.get_opt_string() + defaults[option.dest] = option.check_value(opt_str, default) + return optparse.Values(defaults) + + def error(self, msg): + self.print_usage(sys.stderr) + self.exit(UNKNOWN_ERROR, "%s\n" % msg) + + +def invalid_config_error_message(action, key, val): + """Returns a better error message when invalid configuration option + is provided.""" + if action in ('store_true', 'store_false'): + return ("{0} is not a valid value for {1} option, " + "please specify a boolean value like yes/no, " + "true/false or 1/0 instead.").format(val, key) + + return ("{0} is not a valid value for {1} option, " + "please specify a numerical value like 1/0 " + "instead.").format(val, key) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/status_codes.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/status_codes.py new file mode 100644 index 0000000..275360a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/cli/status_codes.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import + +SUCCESS = 0 +ERROR = 1 +UNKNOWN_ERROR = 2 +VIRTUALENV_NOT_FOUND = 3 +PREVIOUS_BUILD_DIR_ERROR = 4 +NO_MATCHES_FOUND = 23 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/__init__.py new file mode 100644 index 0000000..c7d1da3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/__init__.py @@ -0,0 +1,79 @@ +""" +Package containing all pip commands +""" +from __future__ import absolute_import + +from pip._internal.commands.completion import CompletionCommand +from pip._internal.commands.configuration import ConfigurationCommand +from pip._internal.commands.download import DownloadCommand +from pip._internal.commands.freeze import FreezeCommand +from pip._internal.commands.hash import HashCommand +from pip._internal.commands.help import HelpCommand +from pip._internal.commands.list import ListCommand +from pip._internal.commands.check import CheckCommand +from pip._internal.commands.search import SearchCommand +from pip._internal.commands.show import ShowCommand +from pip._internal.commands.install import InstallCommand +from pip._internal.commands.uninstall import UninstallCommand +from pip._internal.commands.wheel import WheelCommand + +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import List, Type # noqa: F401 + from pip._internal.cli.base_command import Command # noqa: F401 + +commands_order = [ + InstallCommand, + DownloadCommand, + UninstallCommand, + FreezeCommand, + ListCommand, + ShowCommand, + CheckCommand, + ConfigurationCommand, + SearchCommand, + WheelCommand, + HashCommand, + CompletionCommand, + HelpCommand, +] # type: List[Type[Command]] + +commands_dict = {c.name: c for c in commands_order} + + +def get_summaries(ordered=True): + """Yields sorted (command name, command summary) tuples.""" + + if ordered: + cmditems = _sort_commands(commands_dict, commands_order) + else: + cmditems = commands_dict.items() + + for name, command_class in cmditems: + yield (name, command_class.summary) + + +def get_similar_commands(name): + """Command name auto-correct.""" + from difflib import get_close_matches + + name = name.lower() + + close_commands = get_close_matches(name, commands_dict.keys()) + + if close_commands: + return close_commands[0] + else: + return False + + +def _sort_commands(cmddict, order): + def keyfn(key): + try: + return order.index(key[1]) + except ValueError: + # unordered items should come last + return 0xff + + return sorted(cmddict.items(), key=keyfn) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/check.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/check.py new file mode 100644 index 0000000..801cecc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/check.py @@ -0,0 +1,41 @@ +import logging + +from pip._internal.cli.base_command import Command +from pip._internal.operations.check import ( + check_package_set, create_package_set_from_installed, +) + +logger = logging.getLogger(__name__) + + +class CheckCommand(Command): + """Verify installed packages have compatible dependencies.""" + name = 'check' + usage = """ + %prog [options]""" + summary = 'Verify installed packages have compatible dependencies.' + + def run(self, options, args): + package_set, parsing_probs = create_package_set_from_installed() + missing, conflicting = check_package_set(package_set) + + for project_name in missing: + version = package_set[project_name].version + for dependency in missing[project_name]: + logger.info( + "%s %s requires %s, which is not installed.", + project_name, version, dependency[0], + ) + + for project_name in conflicting: + version = package_set[project_name].version + for dep_name, dep_version, req in conflicting[project_name]: + logger.info( + "%s %s has requirement %s, but you have %s %s.", + project_name, version, req, dep_name, dep_version, + ) + + if missing or conflicting or parsing_probs: + return 1 + else: + logger.info("No broken requirements found.") diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/completion.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/completion.py new file mode 100644 index 0000000..2fcdd39 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/completion.py @@ -0,0 +1,94 @@ +from __future__ import absolute_import + +import sys +import textwrap + +from pip._internal.cli.base_command import Command +from pip._internal.utils.misc import get_prog + +BASE_COMPLETION = """ +# pip %(shell)s completion start%(script)s# pip %(shell)s completion end +""" + +COMPLETION_SCRIPTS = { + 'bash': """ + _pip_completion() + { + COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\ + COMP_CWORD=$COMP_CWORD \\ + PIP_AUTO_COMPLETE=1 $1 ) ) + } + complete -o default -F _pip_completion %(prog)s + """, + 'zsh': """ + function _pip_completion { + local words cword + read -Ac words + read -cn cword + reply=( $( COMP_WORDS="$words[*]" \\ + COMP_CWORD=$(( cword-1 )) \\ + PIP_AUTO_COMPLETE=1 $words[1] ) ) + } + compctl -K _pip_completion %(prog)s + """, + 'fish': """ + function __fish_complete_pip + set -lx COMP_WORDS (commandline -o) "" + set -lx COMP_CWORD ( \\ + math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\ + ) + set -lx PIP_AUTO_COMPLETE 1 + string split \\ -- (eval $COMP_WORDS[1]) + end + complete -fa "(__fish_complete_pip)" -c %(prog)s + """, +} + + +class CompletionCommand(Command): + """A helper command to be used for command completion.""" + name = 'completion' + summary = 'A helper command used for command completion.' + ignore_require_venv = True + + def __init__(self, *args, **kw): + super(CompletionCommand, self).__init__(*args, **kw) + + cmd_opts = self.cmd_opts + + cmd_opts.add_option( + '--bash', '-b', + action='store_const', + const='bash', + dest='shell', + help='Emit completion code for bash') + cmd_opts.add_option( + '--zsh', '-z', + action='store_const', + const='zsh', + dest='shell', + help='Emit completion code for zsh') + cmd_opts.add_option( + '--fish', '-f', + action='store_const', + const='fish', + dest='shell', + help='Emit completion code for fish') + + self.parser.insert_option_group(0, cmd_opts) + + def run(self, options, args): + """Prints the completion code of the given shell""" + shells = COMPLETION_SCRIPTS.keys() + shell_options = ['--' + shell for shell in sorted(shells)] + if options.shell in shells: + script = textwrap.dedent( + COMPLETION_SCRIPTS.get(options.shell, '') % { + 'prog': get_prog(), + } + ) + print(BASE_COMPLETION % {'script': script, 'shell': options.shell}) + else: + sys.stderr.write( + 'ERROR: You must pass %s\n' % ' or '.join(shell_options) + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/configuration.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/configuration.py new file mode 100644 index 0000000..826c08d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/configuration.py @@ -0,0 +1,227 @@ +import logging +import os +import subprocess + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.configuration import Configuration, kinds +from pip._internal.exceptions import PipError +from pip._internal.locations import venv_config_file +from pip._internal.utils.misc import get_prog + +logger = logging.getLogger(__name__) + + +class ConfigurationCommand(Command): + """Manage local and global configuration. + + Subcommands: + + 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 + + If none of --user, --global and --venv are passed, a virtual + environment configuration file is used if one is active and the file + exists. Otherwise, all modifications happen on the to the user file by + default. + """ + + name = 'config' + usage = """ + %prog [] list + %prog [] [--editor ] edit + + %prog [] get name + %prog [] set name value + %prog [] unset name + """ + + summary = "Manage local and global configuration." + + def __init__(self, *args, **kwargs): + super(ConfigurationCommand, self).__init__(*args, **kwargs) + + self.configuration = None + + self.cmd_opts.add_option( + '--editor', + dest='editor', + action='store', + default=None, + help=( + 'Editor to use to edit the file. Uses VISUAL or EDITOR ' + 'environment variables if not provided.' + ) + ) + + self.cmd_opts.add_option( + '--global', + dest='global_file', + action='store_true', + default=False, + help='Use the system-wide configuration file only' + ) + + self.cmd_opts.add_option( + '--user', + dest='user_file', + action='store_true', + default=False, + help='Use the user configuration file only' + ) + + self.cmd_opts.add_option( + '--venv', + dest='venv_file', + action='store_true', + default=False, + help='Use the virtualenv configuration file only' + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options, args): + handlers = { + "list": self.list_values, + "edit": self.open_in_editor, + "get": self.get_name, + "set": self.set_name_value, + "unset": self.unset_name + } + + # Determine action + if not args or args[0] not in handlers: + logger.error("Need an action ({}) to perform.".format( + ", ".join(sorted(handlers))) + ) + return ERROR + + action = args[0] + + # Determine which configuration files are to be loaded + # Depends on whether the command is modifying. + try: + load_only = self._determine_file( + options, need_value=(action in ["get", "set", "unset", "edit"]) + ) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + # Load a new configuration + self.configuration = Configuration( + isolated=options.isolated_mode, load_only=load_only + ) + self.configuration.load() + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def _determine_file(self, options, need_value): + file_options = { + kinds.USER: options.user_file, + kinds.GLOBAL: options.global_file, + kinds.VENV: options.venv_file + } + + if sum(file_options.values()) == 0: + if not need_value: + return None + # Default to user, unless there's a virtualenv file. + elif os.path.exists(venv_config_file): + return kinds.VENV + else: + return kinds.USER + elif sum(file_options.values()) == 1: + # There's probably a better expression for this. + return [key for key in file_options if file_options[key]][0] + + raise PipError( + "Need exactly one file to operate upon " + "(--user, --venv, --global) to perform." + ) + + def list_values(self, options, args): + self._get_n_args(args, "list", n=0) + + for key, value in sorted(self.configuration.items()): + logger.info("%s=%r", key, value) + + def get_name(self, options, args): + key = self._get_n_args(args, "get [name]", n=1) + value = self.configuration.get_value(key) + + logger.info("%s", value) + + def set_name_value(self, options, args): + key, value = self._get_n_args(args, "set [name] [value]", n=2) + self.configuration.set_value(key, value) + + self._save_configuration() + + def unset_name(self, options, args): + key = self._get_n_args(args, "unset [name]", n=1) + self.configuration.unset_value(key) + + self._save_configuration() + + def open_in_editor(self, options, args): + editor = self._determine_editor(options) + + fname = self.configuration.get_file_to_edit() + if fname is None: + raise PipError("Could not determine appropriate file.") + + try: + subprocess.check_call([editor, fname]) + except subprocess.CalledProcessError as e: + raise PipError( + "Editor Subprocess exited with exit code {}" + .format(e.returncode) + ) + + def _get_n_args(self, args, example, n): + """Helper to make sure the command got the right number of arguments + """ + if len(args) != n: + msg = ( + 'Got unexpected number of arguments, expected {}. ' + '(example: "{} config {}")' + ).format(n, get_prog(), example) + raise PipError(msg) + + if n == 1: + return args[0] + else: + return args + + def _save_configuration(self): + # We successfully ran a modifying command. Need to save the + # configuration. + try: + self.configuration.save() + except Exception: + logger.error( + "Unable to save configuration. Please report this as a bug.", + exc_info=1 + ) + raise PipError("Internal Error.") + + def _determine_editor(self, options): + if options.editor is not None: + return options.editor + elif "VISUAL" in os.environ: + return os.environ["VISUAL"] + elif "EDITOR" in os.environ: + return os.environ["EDITOR"] + else: + raise PipError("Could not determine editor to use.") diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/download.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/download.py new file mode 100644 index 0000000..a57e4bc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/download.py @@ -0,0 +1,176 @@ +from __future__ import absolute_import + +import logging +import os + +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import RequirementCommand +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req import RequirementSet +from pip._internal.req.req_tracker import RequirementTracker +from pip._internal.resolve import Resolver +from pip._internal.utils.filesystem import check_path_owner +from pip._internal.utils.misc import ensure_dir, normalize_path +from pip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +class DownloadCommand(RequirementCommand): + """ + Download packages from: + + - PyPI (and other indexes) using requirement specifiers. + - VCS project urls. + - Local project directories. + - Local or remote source archives. + + pip also supports downloading from "requirements files", which provide + an easy way to specify a whole environment to be downloaded. + """ + name = 'download' + + usage = """ + %prog [options] [package-index-options] ... + %prog [options] -r [package-index-options] ... + %prog [options] ... + %prog [options] ... + %prog [options] ...""" + + summary = 'Download packages.' + + def __init__(self, *args, **kw): + super(DownloadCommand, self).__init__(*args, **kw) + + cmd_opts = self.cmd_opts + + cmd_opts.add_option(cmdoptions.constraints()) + cmd_opts.add_option(cmdoptions.requirements()) + cmd_opts.add_option(cmdoptions.build_dir()) + cmd_opts.add_option(cmdoptions.no_deps()) + cmd_opts.add_option(cmdoptions.global_options()) + cmd_opts.add_option(cmdoptions.no_binary()) + cmd_opts.add_option(cmdoptions.only_binary()) + cmd_opts.add_option(cmdoptions.prefer_binary()) + cmd_opts.add_option(cmdoptions.src()) + cmd_opts.add_option(cmdoptions.pre()) + cmd_opts.add_option(cmdoptions.no_clean()) + cmd_opts.add_option(cmdoptions.require_hashes()) + cmd_opts.add_option(cmdoptions.progress_bar()) + cmd_opts.add_option(cmdoptions.no_build_isolation()) + cmd_opts.add_option(cmdoptions.use_pep517()) + cmd_opts.add_option(cmdoptions.no_use_pep517()) + + cmd_opts.add_option( + '-d', '--dest', '--destination-dir', '--destination-directory', + dest='download_dir', + metavar='dir', + default=os.curdir, + help=("Download packages into ."), + ) + + cmd_opts.add_option(cmdoptions.platform()) + cmd_opts.add_option(cmdoptions.python_version()) + cmd_opts.add_option(cmdoptions.implementation()) + cmd_opts.add_option(cmdoptions.abi()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, cmd_opts) + + def run(self, options, args): + options.ignore_installed = True + # editable doesn't really make sense for `pip download`, but the bowels + # of the RequirementSet code require that property. + options.editables = [] + + if options.python_version: + python_versions = [options.python_version] + else: + python_versions = None + + cmdoptions.check_dist_restriction(options) + + options.src_dir = os.path.abspath(options.src_dir) + options.download_dir = normalize_path(options.download_dir) + + ensure_dir(options.download_dir) + + with self._build_session(options) as session: + finder = self._build_package_finder( + options=options, + session=session, + platform=options.platform, + python_versions=python_versions, + abi=options.abi, + implementation=options.implementation, + ) + build_delete = (not (options.no_clean or options.build_dir)) + if options.cache_dir and not check_path_owner(options.cache_dir): + logger.warning( + "The directory '%s' or its parent directory is not owned " + "by the current user and caching wheels has been " + "disabled. check the permissions and owner of that " + "directory. If executing pip with sudo, you may want " + "sudo's -H flag.", + options.cache_dir, + ) + options.cache_dir = None + + with RequirementTracker() as req_tracker, TempDirectory( + options.build_dir, delete=build_delete, kind="download" + ) as directory: + + requirement_set = RequirementSet( + require_hashes=options.require_hashes, + ) + self.populate_requirement_set( + requirement_set, + args, + options, + finder, + session, + self.name, + None + ) + + preparer = RequirementPreparer( + build_dir=directory.path, + src_dir=options.src_dir, + download_dir=options.download_dir, + wheel_download_dir=None, + progress_bar=options.progress_bar, + build_isolation=options.build_isolation, + req_tracker=req_tracker, + ) + + resolver = Resolver( + preparer=preparer, + finder=finder, + session=session, + wheel_cache=None, + use_user_site=False, + upgrade_strategy="to-satisfy-only", + force_reinstall=False, + ignore_dependencies=options.ignore_dependencies, + ignore_requires_python=False, + ignore_installed=True, + isolated=options.isolated_mode, + ) + resolver.resolve(requirement_set) + + downloaded = ' '.join([ + req.name for req in requirement_set.successfully_downloaded + ]) + if downloaded: + logger.info('Successfully downloaded %s', downloaded) + + # Clean up + if not options.no_clean: + requirement_set.cleanup_files() + + return requirement_set diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/freeze.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/freeze.py new file mode 100644 index 0000000..dc9c53a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/freeze.py @@ -0,0 +1,96 @@ +from __future__ import absolute_import + +import sys + +from pip._internal.cache import WheelCache +from pip._internal.cli.base_command import Command +from pip._internal.models.format_control import FormatControl +from pip._internal.operations.freeze import freeze +from pip._internal.utils.compat import stdlib_pkgs + +DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'} + + +class FreezeCommand(Command): + """ + Output installed packages in requirements format. + + packages are listed in a case-insensitive sorted order. + """ + name = 'freeze' + usage = """ + %prog [options]""" + summary = 'Output installed packages in requirements format.' + log_streams = ("ext://sys.stderr", "ext://sys.stderr") + + def __init__(self, *args, **kw): + super(FreezeCommand, self).__init__(*args, **kw) + + self.cmd_opts.add_option( + '-r', '--requirement', + dest='requirements', + action='append', + default=[], + metavar='file', + help="Use the order in the given requirements file and its " + "comments when generating output. This option can be " + "used multiple times.") + self.cmd_opts.add_option( + '-f', '--find-links', + dest='find_links', + action='append', + default=[], + metavar='URL', + help='URL for finding packages, which will be added to the ' + 'output.') + self.cmd_opts.add_option( + '-l', '--local', + dest='local', + action='store_true', + default=False, + help='If in a virtualenv that has global access, do not output ' + '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( + '--all', + dest='freeze_all', + action='store_true', + help='Do not skip these packages in the output:' + ' %s' % ', '.join(DEV_PKGS)) + self.cmd_opts.add_option( + '--exclude-editable', + dest='exclude_editable', + action='store_true', + help='Exclude editable package from output.') + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options, args): + format_control = FormatControl(set(), set()) + wheel_cache = WheelCache(options.cache_dir, format_control) + skip = set(stdlib_pkgs) + if not options.freeze_all: + skip.update(DEV_PKGS) + + freeze_kwargs = dict( + requirement=options.requirements, + find_links=options.find_links, + local_only=options.local, + user_only=options.user, + skip_regex=options.skip_requirements_regex, + isolated=options.isolated_mode, + wheel_cache=wheel_cache, + skip=skip, + exclude_editable=options.exclude_editable, + ) + + try: + for line in freeze(**freeze_kwargs): + sys.stdout.write(line + '\n') + finally: + wheel_cache.cleanup() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/hash.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/hash.py new file mode 100644 index 0000000..423440e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/hash.py @@ -0,0 +1,57 @@ +from __future__ import absolute_import + +import hashlib +import logging +import sys + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR +from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES +from pip._internal.utils.misc import read_chunks + +logger = logging.getLogger(__name__) + + +class HashCommand(Command): + """ + Compute a hash of a local package archive. + + These can be used with --hash in a requirements file to do repeatable + installs. + + """ + name = 'hash' + usage = '%prog [options] ...' + summary = 'Compute hashes of package archives.' + ignore_require_venv = True + + def __init__(self, *args, **kw): + super(HashCommand, self).__init__(*args, **kw) + self.cmd_opts.add_option( + '-a', '--algorithm', + dest='algorithm', + choices=STRONG_HASHES, + action='store', + default=FAVORITE_HASH, + help='The hash algorithm to use: one of %s' % + ', '.join(STRONG_HASHES)) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options, args): + if not args: + self.parser.print_usage(sys.stderr) + return ERROR + + algorithm = options.algorithm + for path in args: + logger.info('%s:\n--hash=%s:%s', + path, algorithm, _hash_of_file(path, algorithm)) + + +def _hash_of_file(path, algorithm): + """Return the hash digest of a file.""" + with open(path, 'rb') as archive: + hash = hashlib.new(algorithm) + for chunk in read_chunks(archive): + hash.update(chunk) + return hash.hexdigest() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/help.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/help.py new file mode 100644 index 0000000..49a81cb --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/help.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.exceptions import CommandError + + +class HelpCommand(Command): + """Show help for commands""" + name = 'help' + usage = """ + %prog """ + summary = 'Show help for commands.' + ignore_require_venv = True + + def run(self, options, args): + from pip._internal.commands import commands_dict, get_similar_commands + + try: + # 'pip help' with no args is handled by pip.__init__.parseopt() + cmd_name = args[0] # the command we need help for + except IndexError: + return SUCCESS + + if cmd_name not in commands_dict: + guess = get_similar_commands(cmd_name) + + msg = ['unknown command "%s"' % cmd_name] + if guess: + msg.append('maybe you meant "%s"' % guess) + + raise CommandError(' - '.join(msg)) + + command = commands_dict[cmd_name]() + command.parser.print_help() + + return SUCCESS diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/install.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/install.py new file mode 100644 index 0000000..1c244d2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/install.py @@ -0,0 +1,566 @@ +from __future__ import absolute_import + +import errno +import logging +import operator +import os +import shutil +from optparse import SUPPRESS_HELP + +from pip._vendor import pkg_resources + +from pip._internal.cache import WheelCache +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import RequirementCommand +from pip._internal.cli.status_codes import ERROR +from pip._internal.exceptions import ( + CommandError, InstallationError, PreviousBuildDirError, +) +from pip._internal.locations import distutils_scheme, virtualenv_no_global +from pip._internal.operations.check import check_install_conflicts +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req import RequirementSet, install_given_reqs +from pip._internal.req.req_tracker import RequirementTracker +from pip._internal.resolve import Resolver +from pip._internal.utils.filesystem import check_path_owner +from pip._internal.utils.misc import ( + ensure_dir, get_installed_version, + protect_pip_from_modification_on_windows, +) +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.wheel import WheelBuilder + +logger = logging.getLogger(__name__) + + +class InstallCommand(RequirementCommand): + """ + Install packages from: + + - PyPI (and other indexes) using requirement specifiers. + - VCS project urls. + - Local project directories. + - Local or remote source archives. + + pip also supports installing from "requirements files", which provide + an easy way to specify a whole environment to be installed. + """ + name = 'install' + + usage = """ + %prog [options] [package-index-options] ... + %prog [options] -r [package-index-options] ... + %prog [options] [-e] ... + %prog [options] [-e] ... + %prog [options] ...""" + + summary = 'Install packages.' + + def __init__(self, *args, **kw): + super(InstallCommand, self).__init__(*args, **kw) + + cmd_opts = self.cmd_opts + + cmd_opts.add_option(cmdoptions.requirements()) + cmd_opts.add_option(cmdoptions.constraints()) + cmd_opts.add_option(cmdoptions.no_deps()) + cmd_opts.add_option(cmdoptions.pre()) + + cmd_opts.add_option(cmdoptions.editable()) + cmd_opts.add_option( + '-t', '--target', + dest='target_dir', + metavar='dir', + default=None, + help='Install packages into . ' + 'By default this will not replace existing files/folders in ' + '. Use --upgrade to replace existing packages in ' + 'with new versions.' + ) + cmd_opts.add_option(cmdoptions.platform()) + cmd_opts.add_option(cmdoptions.python_version()) + cmd_opts.add_option(cmdoptions.implementation()) + cmd_opts.add_option(cmdoptions.abi()) + + cmd_opts.add_option( + '--user', + dest='use_user_site', + action='store_true', + help="Install to the Python user install directory for your " + "platform. Typically ~/.local/, or %APPDATA%\\Python on " + "Windows. (See the Python documentation for site.USER_BASE " + "for full details.)") + cmd_opts.add_option( + '--no-user', + dest='use_user_site', + action='store_false', + help=SUPPRESS_HELP) + cmd_opts.add_option( + '--root', + dest='root_path', + metavar='dir', + default=None, + help="Install everything relative to this alternate root " + "directory.") + cmd_opts.add_option( + '--prefix', + dest='prefix_path', + metavar='dir', + default=None, + help="Installation prefix where lib, bin and other top-level " + "folders are placed") + + cmd_opts.add_option(cmdoptions.build_dir()) + + cmd_opts.add_option(cmdoptions.src()) + + cmd_opts.add_option( + '-U', '--upgrade', + dest='upgrade', + action='store_true', + help='Upgrade all specified packages to the newest available ' + 'version. The handling of dependencies depends on the ' + 'upgrade-strategy used.' + ) + + cmd_opts.add_option( + '--upgrade-strategy', + dest='upgrade_strategy', + default='only-if-needed', + choices=['only-if-needed', 'eager'], + help='Determines how dependency upgrading should be handled ' + '[default: %default]. ' + '"eager" - dependencies are upgraded regardless of ' + 'whether the currently installed version satisfies the ' + 'requirements of the upgraded package(s). ' + '"only-if-needed" - are upgraded only when they do not ' + 'satisfy the requirements of the upgraded package(s).' + ) + + cmd_opts.add_option( + '--force-reinstall', + dest='force_reinstall', + action='store_true', + help='Reinstall all packages even if they are already ' + 'up-to-date.') + + cmd_opts.add_option( + '-I', '--ignore-installed', + dest='ignore_installed', + action='store_true', + help='Ignore the installed packages (reinstalling instead).') + + cmd_opts.add_option(cmdoptions.ignore_requires_python()) + cmd_opts.add_option(cmdoptions.no_build_isolation()) + cmd_opts.add_option(cmdoptions.use_pep517()) + cmd_opts.add_option(cmdoptions.no_use_pep517()) + + cmd_opts.add_option(cmdoptions.install_options()) + cmd_opts.add_option(cmdoptions.global_options()) + + cmd_opts.add_option( + "--compile", + action="store_true", + dest="compile", + default=True, + help="Compile Python source files to bytecode", + ) + + cmd_opts.add_option( + "--no-compile", + action="store_false", + dest="compile", + help="Do not compile Python source files to bytecode", + ) + + cmd_opts.add_option( + "--no-warn-script-location", + action="store_false", + dest="warn_script_location", + default=True, + help="Do not warn when installing scripts outside PATH", + ) + cmd_opts.add_option( + "--no-warn-conflicts", + action="store_false", + dest="warn_about_conflicts", + default=True, + help="Do not warn about broken dependencies", + ) + + cmd_opts.add_option(cmdoptions.no_binary()) + cmd_opts.add_option(cmdoptions.only_binary()) + cmd_opts.add_option(cmdoptions.prefer_binary()) + cmd_opts.add_option(cmdoptions.no_clean()) + cmd_opts.add_option(cmdoptions.require_hashes()) + cmd_opts.add_option(cmdoptions.progress_bar()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, cmd_opts) + + def run(self, options, args): + cmdoptions.check_install_build_global(options) + upgrade_strategy = "to-satisfy-only" + if options.upgrade: + upgrade_strategy = options.upgrade_strategy + + if options.build_dir: + options.build_dir = os.path.abspath(options.build_dir) + + cmdoptions.check_dist_restriction(options, check_target=True) + + if options.python_version: + python_versions = [options.python_version] + else: + python_versions = None + + options.src_dir = os.path.abspath(options.src_dir) + install_options = options.install_options or [] + if options.use_user_site: + if options.prefix_path: + raise CommandError( + "Can not combine '--user' and '--prefix' as they imply " + "different installation locations" + ) + if virtualenv_no_global(): + raise InstallationError( + "Can not perform a '--user' install. User site-packages " + "are not visible in this virtualenv." + ) + install_options.append('--user') + install_options.append('--prefix=') + + target_temp_dir = TempDirectory(kind="target") + if options.target_dir: + options.ignore_installed = True + options.target_dir = os.path.abspath(options.target_dir) + if (os.path.exists(options.target_dir) and not + os.path.isdir(options.target_dir)): + raise CommandError( + "Target path exists but is not a directory, will not " + "continue." + ) + + # Create a target directory for using with the target option + target_temp_dir.create() + install_options.append('--home=' + target_temp_dir.path) + + global_options = options.global_options or [] + + with self._build_session(options) as session: + finder = self._build_package_finder( + options=options, + session=session, + platform=options.platform, + python_versions=python_versions, + abi=options.abi, + implementation=options.implementation, + ) + build_delete = (not (options.no_clean or options.build_dir)) + wheel_cache = WheelCache(options.cache_dir, options.format_control) + + if options.cache_dir and not check_path_owner(options.cache_dir): + logger.warning( + "The directory '%s' or its parent directory is not owned " + "by the current user and caching wheels has been " + "disabled. check the permissions and owner of that " + "directory. If executing pip with sudo, you may want " + "sudo's -H flag.", + options.cache_dir, + ) + options.cache_dir = None + + with RequirementTracker() as req_tracker, TempDirectory( + options.build_dir, delete=build_delete, kind="install" + ) as directory: + requirement_set = RequirementSet( + require_hashes=options.require_hashes, + check_supported_wheels=not options.target_dir, + ) + + try: + self.populate_requirement_set( + requirement_set, args, options, finder, session, + self.name, wheel_cache + ) + preparer = RequirementPreparer( + build_dir=directory.path, + src_dir=options.src_dir, + download_dir=None, + wheel_download_dir=None, + progress_bar=options.progress_bar, + build_isolation=options.build_isolation, + req_tracker=req_tracker, + ) + + resolver = Resolver( + preparer=preparer, + finder=finder, + session=session, + wheel_cache=wheel_cache, + use_user_site=options.use_user_site, + upgrade_strategy=upgrade_strategy, + force_reinstall=options.force_reinstall, + ignore_dependencies=options.ignore_dependencies, + ignore_requires_python=options.ignore_requires_python, + ignore_installed=options.ignore_installed, + isolated=options.isolated_mode, + use_pep517=options.use_pep517 + ) + resolver.resolve(requirement_set) + + protect_pip_from_modification_on_windows( + modifying_pip=requirement_set.has_requirement("pip") + ) + + # Consider legacy and PEP517-using requirements separately + legacy_requirements = [] + pep517_requirements = [] + for req in requirement_set.requirements.values(): + if req.use_pep517: + pep517_requirements.append(req) + else: + legacy_requirements.append(req) + + # We don't build wheels for legacy requirements if we + # don't have wheel installed or we don't have a cache dir + try: + import wheel # noqa: F401 + build_legacy = bool(options.cache_dir) + except ImportError: + build_legacy = False + + wb = WheelBuilder( + finder, preparer, wheel_cache, + build_options=[], global_options=[], + ) + + # Always build PEP 517 requirements + build_failures = wb.build( + pep517_requirements, + session=session, autobuilding=True + ) + + if build_legacy: + # We don't care about failures building legacy + # requirements, as we'll fall through to a direct + # install for those. + wb.build( + legacy_requirements, + session=session, autobuilding=True + ) + + # If we're using PEP 517, we cannot do a direct install + # so we fail here. + if build_failures: + raise InstallationError( + "Could not build wheels for {} which use" + " PEP 517 and cannot be installed directly".format( + ", ".join(r.name for r in build_failures))) + + to_install = resolver.get_installation_order( + requirement_set + ) + + # Consistency Checking of the package set we're installing. + should_warn_about_conflicts = ( + not options.ignore_dependencies and + options.warn_about_conflicts + ) + if should_warn_about_conflicts: + self._warn_about_conflicts(to_install) + + # Don't warn about script install locations if + # --target has been specified + warn_script_location = options.warn_script_location + if options.target_dir: + warn_script_location = False + + installed = install_given_reqs( + to_install, + install_options, + global_options, + root=options.root_path, + home=target_temp_dir.path, + prefix=options.prefix_path, + pycompile=options.compile, + warn_script_location=warn_script_location, + use_user_site=options.use_user_site, + ) + + lib_locations = get_lib_location_guesses( + user=options.use_user_site, + home=target_temp_dir.path, + root=options.root_path, + prefix=options.prefix_path, + isolated=options.isolated_mode, + ) + working_set = pkg_resources.WorkingSet(lib_locations) + + reqs = sorted(installed, key=operator.attrgetter('name')) + items = [] + for req in reqs: + item = req.name + try: + installed_version = get_installed_version( + req.name, working_set=working_set + ) + if installed_version: + item += '-' + installed_version + except Exception: + pass + items.append(item) + installed = ' '.join(items) + if installed: + logger.info('Successfully installed %s', installed) + except EnvironmentError as error: + show_traceback = (self.verbosity >= 1) + + message = create_env_error_message( + error, show_traceback, options.use_user_site, + ) + logger.error(message, exc_info=show_traceback) + + return ERROR + except PreviousBuildDirError: + options.no_clean = True + raise + finally: + # Clean up + if not options.no_clean: + requirement_set.cleanup_files() + wheel_cache.cleanup() + + if options.target_dir: + self._handle_target_dir( + options.target_dir, target_temp_dir, options.upgrade + ) + return requirement_set + + def _handle_target_dir(self, target_dir, target_temp_dir, upgrade): + ensure_dir(target_dir) + + # Checking both purelib and platlib directories for installed + # packages to be moved to target directory + lib_dir_list = [] + + with target_temp_dir: + # Checking both purelib and platlib directories for installed + # packages to be moved to target directory + scheme = distutils_scheme('', home=target_temp_dir.path) + purelib_dir = scheme['purelib'] + platlib_dir = scheme['platlib'] + data_dir = scheme['data'] + + if os.path.exists(purelib_dir): + lib_dir_list.append(purelib_dir) + if os.path.exists(platlib_dir) and platlib_dir != purelib_dir: + lib_dir_list.append(platlib_dir) + if os.path.exists(data_dir): + lib_dir_list.append(data_dir) + + for lib_dir in lib_dir_list: + for item in os.listdir(lib_dir): + if lib_dir == data_dir: + ddir = os.path.join(data_dir, item) + if any(s.startswith(ddir) for s in lib_dir_list[:-1]): + continue + target_item_dir = os.path.join(target_dir, item) + if os.path.exists(target_item_dir): + if not upgrade: + logger.warning( + 'Target directory %s already exists. Specify ' + '--upgrade to force replacement.', + target_item_dir + ) + continue + if os.path.islink(target_item_dir): + logger.warning( + 'Target directory %s already exists and is ' + 'a link. Pip will not automatically replace ' + 'links, please remove if replacement is ' + 'desired.', + target_item_dir + ) + continue + if os.path.isdir(target_item_dir): + shutil.rmtree(target_item_dir) + else: + os.remove(target_item_dir) + + shutil.move( + os.path.join(lib_dir, item), + target_item_dir + ) + + def _warn_about_conflicts(self, to_install): + try: + package_set, _dep_info = check_install_conflicts(to_install) + except Exception: + logger.error("Error checking for conflicts.", exc_info=True) + return + missing, conflicting = _dep_info + + # NOTE: There is some duplication here from pip check + for project_name in missing: + version = package_set[project_name][0] + for dependency in missing[project_name]: + logger.critical( + "%s %s requires %s, which is not installed.", + project_name, version, dependency[1], + ) + + for project_name in conflicting: + version = package_set[project_name][0] + for dep_name, dep_version, req in conflicting[project_name]: + logger.critical( + "%s %s has requirement %s, but you'll have %s %s which is " + "incompatible.", + project_name, version, req, dep_name, dep_version, + ) + + +def get_lib_location_guesses(*args, **kwargs): + scheme = distutils_scheme('', *args, **kwargs) + return [scheme['purelib'], scheme['platlib']] + + +def create_env_error_message(error, show_traceback, using_user_site): + """Format an error message for an EnvironmentError + + It may occur anytime during the execution of the install command. + """ + parts = [] + + # Mention the error if we are not going to show a traceback + parts.append("Could not install packages due to an EnvironmentError") + if not show_traceback: + parts.append(": ") + parts.append(str(error)) + else: + parts.append(".") + + # Spilt the error indication from a helper message (if any) + parts[-1] += "\n" + + # Suggest useful actions to the user: + # (1) using user site-packages or (2) verifying the permissions + if error.errno == errno.EACCES: + user_option_part = "Consider using the `--user` option" + permissions_part = "Check the permissions" + + if not using_user_site: + parts.extend([ + user_option_part, " or ", + permissions_part.lower(), + ]) + else: + parts.append(permissions_part) + parts.append(".\n") + + return "".join(parts).strip() + "\n" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/list.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/list.py new file mode 100644 index 0000000..a640274 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/list.py @@ -0,0 +1,301 @@ +from __future__ import absolute_import + +import json +import logging + +from pip._vendor import six +from pip._vendor.six.moves import zip_longest + +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.exceptions import CommandError +from pip._internal.index import PackageFinder +from pip._internal.utils.misc import ( + dist_is_editable, get_installed_distributions, +) +from pip._internal.utils.packaging import get_installer + +logger = logging.getLogger(__name__) + + +class ListCommand(Command): + """ + List installed packages, including editables. + + Packages are listed in a case-insensitive sorted order. + """ + name = 'list' + usage = """ + %prog [options]""" + summary = 'List installed packages.' + + def __init__(self, *args, **kw): + super(ListCommand, self).__init__(*args, **kw) + + cmd_opts = self.cmd_opts + + cmd_opts.add_option( + '-o', '--outdated', + action='store_true', + default=False, + help='List outdated packages') + cmd_opts.add_option( + '-u', '--uptodate', + action='store_true', + default=False, + help='List uptodate packages') + cmd_opts.add_option( + '-e', '--editable', + action='store_true', + default=False, + help='List editable projects.') + cmd_opts.add_option( + '-l', '--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.') + + cmd_opts.add_option( + '--pre', + action='store_true', + default=False, + help=("Include pre-release and development versions. By default, " + "pip only finds stable versions."), + ) + + cmd_opts.add_option( + '--format', + action='store', + dest='list_format', + default="columns", + choices=('columns', 'freeze', 'json'), + help="Select the output format among: columns (default), freeze, " + "or json", + ) + + cmd_opts.add_option( + '--not-required', + action='store_true', + dest='not_required', + help="List packages that are not dependencies of " + "installed packages.", + ) + + cmd_opts.add_option( + '--exclude-editable', + action='store_false', + dest='include_editable', + help='Exclude editable package from output.', + ) + cmd_opts.add_option( + '--include-editable', + action='store_true', + dest='include_editable', + help='Include editable package from output.', + default=True, + ) + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, self.parser + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, cmd_opts) + + def _build_package_finder(self, options, index_urls, session): + """ + Create a package finder appropriate to this list command. + """ + return PackageFinder( + find_links=options.find_links, + index_urls=index_urls, + allow_all_prereleases=options.pre, + trusted_hosts=options.trusted_hosts, + session=session, + ) + + def run(self, options, args): + if options.outdated and options.uptodate: + raise CommandError( + "Options --outdated and --uptodate cannot be combined.") + + packages = get_installed_distributions( + local_only=options.local, + user_only=options.user, + editables_only=options.editable, + include_editables=options.include_editable, + ) + + # get_not_required must be called firstly in order to find and + # filter out all dependencies correctly. Otherwise a package + # can't be identified as requirement because some parent packages + # could be filtered out before. + if options.not_required: + packages = self.get_not_required(packages, options) + + if options.outdated: + packages = self.get_outdated(packages, options) + elif options.uptodate: + packages = self.get_uptodate(packages, options) + + self.output_package_listing(packages, options) + + def get_outdated(self, packages, options): + return [ + dist for dist in self.iter_packages_latest_infos(packages, options) + if dist.latest_version > dist.parsed_version + ] + + def get_uptodate(self, packages, options): + return [ + dist for dist in self.iter_packages_latest_infos(packages, options) + if dist.latest_version == dist.parsed_version + ] + + def get_not_required(self, packages, options): + dep_keys = set() + for dist in packages: + dep_keys.update(requirement.key for requirement in dist.requires()) + return {pkg for pkg in packages if pkg.key not in dep_keys} + + def iter_packages_latest_infos(self, packages, options): + index_urls = [options.index_url] + options.extra_index_urls + if options.no_index: + logger.debug('Ignoring indexes: %s', ','.join(index_urls)) + index_urls = [] + + with self._build_session(options) as session: + finder = self._build_package_finder(options, index_urls, session) + + for dist in packages: + typ = 'unknown' + all_candidates = finder.find_all_candidates(dist.key) + if not options.pre: + # Remove prereleases + all_candidates = [candidate for candidate in all_candidates + if not candidate.version.is_prerelease] + + if not all_candidates: + continue + best_candidate = max(all_candidates, + key=finder._candidate_sort_key) + remote_version = best_candidate.version + if best_candidate.location.is_wheel: + typ = 'wheel' + else: + typ = 'sdist' + # This is dirty but makes the rest of the code much cleaner + dist.latest_version = remote_version + dist.latest_filetype = typ + yield dist + + def output_package_listing(self, packages, options): + packages = sorted( + packages, + key=lambda dist: dist.project_name.lower(), + ) + if options.list_format == 'columns' and packages: + data, header = format_for_columns(packages, options) + self.output_package_listing_columns(data, header) + elif options.list_format == 'freeze': + for dist in packages: + if options.verbose >= 1: + logger.info("%s==%s (%s)", dist.project_name, + dist.version, dist.location) + else: + logger.info("%s==%s", dist.project_name, dist.version) + elif options.list_format == 'json': + logger.info(format_for_json(packages, options)) + + def output_package_listing_columns(self, data, header): + # insert the header first: we need to know the size of column names + if len(data) > 0: + data.insert(0, header) + + pkg_strings, sizes = tabulate(data) + + # Create and add a separator. + if len(data) > 0: + pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes))) + + for val in pkg_strings: + logger.info(val) + + +def tabulate(vals): + # From pfmoore on GitHub: + # https://github.com/pypa/pip/issues/3651#issuecomment-216932564 + assert len(vals) > 0 + + sizes = [0] * max(len(x) for x in vals) + for row in vals: + sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)] + + result = [] + for row in vals: + display = " ".join([str(c).ljust(s) if c is not None else '' + for s, c in zip_longest(sizes, row)]) + result.append(display) + + return result, sizes + + +def format_for_columns(pkgs, options): + """ + Convert the package data into something usable + by output_package_listing_columns. + """ + running_outdated = options.outdated + # Adjust the header for the `pip list --outdated` case. + if running_outdated: + header = ["Package", "Version", "Latest", "Type"] + else: + header = ["Package", "Version"] + + data = [] + if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs): + header.append("Location") + if options.verbose >= 1: + header.append("Installer") + + for proj in pkgs: + # if we're working on the 'outdated' list, separate out the + # latest_version and type + row = [proj.project_name, proj.version] + + if running_outdated: + row.append(proj.latest_version) + row.append(proj.latest_filetype) + + if options.verbose >= 1 or dist_is_editable(proj): + row.append(proj.location) + if options.verbose >= 1: + row.append(get_installer(proj)) + + data.append(row) + + return data, header + + +def format_for_json(packages, options): + data = [] + for dist in packages: + info = { + 'name': dist.project_name, + 'version': six.text_type(dist.version), + } + if options.verbose >= 1: + info['location'] = dist.location + info['installer'] = get_installer(dist) + if options.outdated: + info['latest_version'] = six.text_type(dist.latest_version) + info['latest_filetype'] = dist.latest_filetype + data.append(info) + return json.dumps(data) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/search.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/search.py new file mode 100644 index 0000000..c157a31 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/search.py @@ -0,0 +1,135 @@ +from __future__ import absolute_import + +import logging +import sys +import textwrap +from collections import OrderedDict + +from pip._vendor import pkg_resources +from pip._vendor.packaging.version import parse as parse_version +# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is +# why we ignore the type on this import +from pip._vendor.six.moves import xmlrpc_client # type: ignore + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS +from pip._internal.download import PipXmlrpcTransport +from pip._internal.exceptions import CommandError +from pip._internal.models.index import PyPI +from pip._internal.utils.compat import get_terminal_size +from pip._internal.utils.logging import indent_log + +logger = logging.getLogger(__name__) + + +class SearchCommand(Command): + """Search for PyPI packages whose name or summary contains .""" + name = 'search' + usage = """ + %prog [options] """ + summary = 'Search PyPI for packages.' + ignore_require_venv = True + + def __init__(self, *args, **kw): + super(SearchCommand, self).__init__(*args, **kw) + self.cmd_opts.add_option( + '-i', '--index', + dest='index', + metavar='URL', + default=PyPI.pypi_url, + help='Base URL of Python Package Index (default %default)') + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options, args): + if not args: + raise CommandError('Missing required argument (search query).') + query = args + pypi_hits = self.search(query, options) + hits = transform_hits(pypi_hits) + + terminal_width = None + if sys.stdout.isatty(): + terminal_width = get_terminal_size()[0] + + print_results(hits, terminal_width=terminal_width) + if pypi_hits: + return SUCCESS + return NO_MATCHES_FOUND + + def search(self, query, options): + index_url = options.index + with self._build_session(options) as session: + transport = PipXmlrpcTransport(index_url, session) + pypi = xmlrpc_client.ServerProxy(index_url, transport) + hits = pypi.search({'name': query, 'summary': query}, 'or') + return hits + + +def transform_hits(hits): + """ + The list from pypi is really a list of versions. We want a list of + packages with the list of versions stored inline. This converts the + list from pypi into one we can use. + """ + packages = OrderedDict() + for hit in hits: + name = hit['name'] + summary = hit['summary'] + version = hit['version'] + + if name not in packages.keys(): + packages[name] = { + 'name': name, + 'summary': summary, + 'versions': [version], + } + else: + packages[name]['versions'].append(version) + + # if this is the highest version, replace summary and score + if version == highest_version(packages[name]['versions']): + packages[name]['summary'] = summary + + return list(packages.values()) + + +def print_results(hits, name_column_width=None, terminal_width=None): + if not hits: + return + if name_column_width is None: + name_column_width = max([ + len(hit['name']) + len(highest_version(hit.get('versions', ['-']))) + for hit in hits + ]) + 4 + + installed_packages = [p.project_name for p in pkg_resources.working_set] + for hit in hits: + name = hit['name'] + summary = hit['summary'] or '' + latest = highest_version(hit.get('versions', ['-'])) + if terminal_width is not None: + target_width = terminal_width - name_column_width - 5 + if target_width > 10: + # wrap and indent summary to fit terminal + summary = textwrap.wrap(summary, target_width) + summary = ('\n' + ' ' * (name_column_width + 3)).join(summary) + + line = '%-*s - %s' % (name_column_width, + '%s (%s)' % (name, latest), summary) + try: + logger.info(line) + if name in installed_packages: + dist = pkg_resources.get_distribution(name) + with indent_log(): + if dist.version == latest: + logger.info('INSTALLED: %s (latest)', dist.version) + else: + logger.info('INSTALLED: %s', dist.version) + logger.info('LATEST: %s', latest) + except UnicodeEncodeError: + pass + + +def highest_version(versions): + return max(versions, key=parse_version) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/show.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/show.py new file mode 100644 index 0000000..f92c9bc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/show.py @@ -0,0 +1,168 @@ +from __future__ import absolute_import + +import logging +import os +from email.parser import FeedParser # type: ignore + +from pip._vendor import pkg_resources +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS + +logger = logging.getLogger(__name__) + + +class ShowCommand(Command): + """ + Show information about one or more installed packages. + + The output is in RFC-compliant mail header format. + """ + name = 'show' + usage = """ + %prog [options] ...""" + summary = 'Show information about installed packages.' + ignore_require_venv = True + + def __init__(self, *args, **kw): + super(ShowCommand, self).__init__(*args, **kw) + self.cmd_opts.add_option( + '-f', '--files', + dest='files', + action='store_true', + default=False, + help='Show the full list of installed files for each package.') + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options, args): + if not args: + logger.warning('ERROR: Please provide a package name or names.') + return ERROR + query = args + + results = search_packages_info(query) + if not print_results( + results, list_files=options.files, verbose=options.verbose): + return ERROR + return SUCCESS + + +def search_packages_info(query): + """ + Gather details from installed distributions. Print distribution name, + version, location, and installed files. Installed files requires a + pip generated 'installed-files.txt' in the distributions '.egg-info' + directory. + """ + installed = {} + for p in pkg_resources.working_set: + installed[canonicalize_name(p.project_name)] = p + + query_names = [canonicalize_name(name) for name in query] + + for dist in [installed[pkg] for pkg in query_names if pkg in installed]: + package = { + 'name': dist.project_name, + 'version': dist.version, + 'location': dist.location, + 'requires': [dep.project_name for dep in dist.requires()], + } + file_list = None + metadata = None + if isinstance(dist, pkg_resources.DistInfoDistribution): + # RECORDs should be part of .dist-info metadatas + if dist.has_metadata('RECORD'): + lines = dist.get_metadata_lines('RECORD') + paths = [l.split(',')[0] for l in lines] + paths = [os.path.join(dist.location, p) for p in paths] + file_list = [os.path.relpath(p, dist.location) for p in paths] + + if dist.has_metadata('METADATA'): + metadata = dist.get_metadata('METADATA') + else: + # Otherwise use pip's log for .egg-info's + if dist.has_metadata('installed-files.txt'): + paths = dist.get_metadata_lines('installed-files.txt') + paths = [os.path.join(dist.egg_info, p) for p in paths] + file_list = [os.path.relpath(p, dist.location) for p in paths] + + if dist.has_metadata('PKG-INFO'): + metadata = dist.get_metadata('PKG-INFO') + + if dist.has_metadata('entry_points.txt'): + entry_points = dist.get_metadata_lines('entry_points.txt') + package['entry_points'] = entry_points + + if dist.has_metadata('INSTALLER'): + for line in dist.get_metadata_lines('INSTALLER'): + if line.strip(): + package['installer'] = line.strip() + break + + # @todo: Should pkg_resources.Distribution have a + # `get_pkg_info` method? + feed_parser = FeedParser() + feed_parser.feed(metadata) + pkg_info_dict = feed_parser.close() + for key in ('metadata-version', 'summary', + 'home-page', 'author', 'author-email', 'license'): + package[key] = pkg_info_dict.get(key) + + # It looks like FeedParser cannot deal with repeated headers + classifiers = [] + for line in metadata.splitlines(): + if line.startswith('Classifier: '): + classifiers.append(line[len('Classifier: '):]) + package['classifiers'] = classifiers + + if file_list: + package['files'] = sorted(file_list) + yield package + + +def print_results(distributions, list_files=False, verbose=False): + """ + Print the informations from installed distributions found. + """ + results_printed = False + for i, dist in enumerate(distributions): + results_printed = True + if i > 0: + logger.info("---") + + name = dist.get('name', '') + required_by = [ + pkg.project_name for pkg in pkg_resources.working_set + if name in [required.name for required in pkg.requires()] + ] + + logger.info("Name: %s", name) + logger.info("Version: %s", dist.get('version', '')) + logger.info("Summary: %s", dist.get('summary', '')) + logger.info("Home-page: %s", dist.get('home-page', '')) + logger.info("Author: %s", dist.get('author', '')) + logger.info("Author-email: %s", dist.get('author-email', '')) + logger.info("License: %s", dist.get('license', '')) + logger.info("Location: %s", dist.get('location', '')) + logger.info("Requires: %s", ', '.join(dist.get('requires', []))) + logger.info("Required-by: %s", ', '.join(required_by)) + + if verbose: + logger.info("Metadata-Version: %s", + dist.get('metadata-version', '')) + logger.info("Installer: %s", dist.get('installer', '')) + logger.info("Classifiers:") + for classifier in dist.get('classifiers', []): + logger.info(" %s", classifier) + logger.info("Entry-points:") + for entry in dist.get('entry_points', []): + logger.info(" %s", entry.strip()) + if list_files: + logger.info("Files:") + for line in dist.get('files', []): + logger.info(" %s", line.strip()) + if "files" not in dist: + logger.info("Cannot locate installed-files.txt") + return results_printed diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/uninstall.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/uninstall.py new file mode 100644 index 0000000..0cd6f54 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/uninstall.py @@ -0,0 +1,78 @@ +from __future__ import absolute_import + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.cli.base_command import Command +from pip._internal.exceptions import InstallationError +from pip._internal.req import parse_requirements +from pip._internal.req.constructors import install_req_from_line +from pip._internal.utils.misc import protect_pip_from_modification_on_windows + + +class UninstallCommand(Command): + """ + Uninstall packages. + + pip is able to uninstall most installed packages. Known exceptions are: + + - Pure distutils packages installed with ``python setup.py install``, which + leave behind no metadata to determine what files were installed. + - Script wrappers installed by ``python setup.py develop``. + """ + name = 'uninstall' + usage = """ + %prog [options] ... + %prog [options] -r ...""" + summary = 'Uninstall packages.' + + def __init__(self, *args, **kw): + super(UninstallCommand, self).__init__(*args, **kw) + self.cmd_opts.add_option( + '-r', '--requirement', + dest='requirements', + action='append', + default=[], + metavar='file', + help='Uninstall all the packages listed in the given requirements ' + 'file. This option can be used multiple times.', + ) + self.cmd_opts.add_option( + '-y', '--yes', + dest='yes', + action='store_true', + help="Don't ask for confirmation of uninstall deletions.") + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options, args): + with self._build_session(options) as session: + reqs_to_uninstall = {} + for name in args: + req = install_req_from_line( + name, isolated=options.isolated_mode, + ) + if req.name: + reqs_to_uninstall[canonicalize_name(req.name)] = req + for filename in options.requirements: + for req in parse_requirements( + filename, + options=options, + session=session): + if req.name: + reqs_to_uninstall[canonicalize_name(req.name)] = req + if not reqs_to_uninstall: + raise InstallationError( + 'You must give at least one requirement to %(name)s (see ' + '"pip help %(name)s")' % dict(name=self.name) + ) + + protect_pip_from_modification_on_windows( + modifying_pip="pip" in reqs_to_uninstall + ) + + for req in reqs_to_uninstall.values(): + uninstall_pathset = req.uninstall( + auto_confirm=options.yes, verbose=self.verbosity > 0, + ) + if uninstall_pathset: + uninstall_pathset.commit() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/wheel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/wheel.py new file mode 100644 index 0000000..cd72a3d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/commands/wheel.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import logging +import os + +from pip._internal.cache import WheelCache +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import RequirementCommand +from pip._internal.exceptions import CommandError, PreviousBuildDirError +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req import RequirementSet +from pip._internal.req.req_tracker import RequirementTracker +from pip._internal.resolve import Resolver +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.wheel import WheelBuilder + +logger = logging.getLogger(__name__) + + +class WheelCommand(RequirementCommand): + """ + Build Wheel archives for your requirements and dependencies. + + Wheel is a built-package format, and offers the advantage of not + 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. + + """ + + name = 'wheel' + usage = """ + %prog [options] ... + %prog [options] -r ... + %prog [options] [-e] ... + %prog [options] [-e] ... + %prog [options] ...""" + + summary = 'Build wheels from your requirements.' + + def __init__(self, *args, **kw): + super(WheelCommand, self).__init__(*args, **kw) + + cmd_opts = self.cmd_opts + + cmd_opts.add_option( + '-w', '--wheel-dir', + dest='wheel_dir', + metavar='dir', + default=os.curdir, + help=("Build wheels into , where the default is the " + "current working directory."), + ) + cmd_opts.add_option(cmdoptions.no_binary()) + cmd_opts.add_option(cmdoptions.only_binary()) + cmd_opts.add_option(cmdoptions.prefer_binary()) + cmd_opts.add_option( + '--build-option', + dest='build_options', + metavar='options', + action='append', + help="Extra arguments to be supplied to 'setup.py bdist_wheel'.", + ) + cmd_opts.add_option(cmdoptions.no_build_isolation()) + cmd_opts.add_option(cmdoptions.use_pep517()) + cmd_opts.add_option(cmdoptions.no_use_pep517()) + cmd_opts.add_option(cmdoptions.constraints()) + cmd_opts.add_option(cmdoptions.editable()) + cmd_opts.add_option(cmdoptions.requirements()) + cmd_opts.add_option(cmdoptions.src()) + cmd_opts.add_option(cmdoptions.ignore_requires_python()) + cmd_opts.add_option(cmdoptions.no_deps()) + cmd_opts.add_option(cmdoptions.build_dir()) + cmd_opts.add_option(cmdoptions.progress_bar()) + + cmd_opts.add_option( + '--global-option', + dest='global_options', + action='append', + metavar='options', + help="Extra global options to be supplied to the setup.py " + "call before the 'bdist_wheel' command.") + + cmd_opts.add_option( + '--pre', + action='store_true', + default=False, + help=("Include pre-release and development versions. By default, " + "pip only finds stable versions."), + ) + + cmd_opts.add_option(cmdoptions.no_clean()) + cmd_opts.add_option(cmdoptions.require_hashes()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, cmd_opts) + + def run(self, options, args): + cmdoptions.check_install_build_global(options) + + index_urls = [options.index_url] + options.extra_index_urls + if options.no_index: + logger.debug('Ignoring indexes: %s', ','.join(index_urls)) + index_urls = [] + + if options.build_dir: + options.build_dir = os.path.abspath(options.build_dir) + + options.src_dir = os.path.abspath(options.src_dir) + + with self._build_session(options) as session: + finder = self._build_package_finder(options, session) + build_delete = (not (options.no_clean or options.build_dir)) + wheel_cache = WheelCache(options.cache_dir, options.format_control) + + with RequirementTracker() as req_tracker, TempDirectory( + options.build_dir, delete=build_delete, kind="wheel" + ) as directory: + + requirement_set = RequirementSet( + require_hashes=options.require_hashes, + ) + + try: + self.populate_requirement_set( + requirement_set, args, options, finder, session, + self.name, wheel_cache + ) + + preparer = RequirementPreparer( + build_dir=directory.path, + src_dir=options.src_dir, + download_dir=None, + wheel_download_dir=options.wheel_dir, + progress_bar=options.progress_bar, + build_isolation=options.build_isolation, + req_tracker=req_tracker, + ) + + resolver = Resolver( + preparer=preparer, + finder=finder, + session=session, + wheel_cache=wheel_cache, + use_user_site=False, + upgrade_strategy="to-satisfy-only", + force_reinstall=False, + ignore_dependencies=options.ignore_dependencies, + ignore_requires_python=options.ignore_requires_python, + ignore_installed=True, + isolated=options.isolated_mode, + use_pep517=options.use_pep517 + ) + resolver.resolve(requirement_set) + + # build wheels + wb = WheelBuilder( + finder, preparer, wheel_cache, + build_options=options.build_options or [], + global_options=options.global_options or [], + no_clean=options.no_clean, + ) + build_failures = wb.build( + requirement_set.requirements.values(), session=session, + ) + if len(build_failures) != 0: + raise CommandError( + "Failed to build one or more wheels" + ) + except PreviousBuildDirError: + options.no_clean = True + raise + finally: + if not options.no_clean: + requirement_set.cleanup_files() + wheel_cache.cleanup() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/configuration.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/configuration.py new file mode 100644 index 0000000..fe6df9b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/configuration.py @@ -0,0 +1,387 @@ +"""Configuration management setup + +Some terminology: +- name + As written in config files. +- value + Value associated with a name +- key + Name combined with it's section (section.name) +- variant + A single word describing where the configuration key-value pair came from +""" + +import locale +import logging +import os + +from pip._vendor import six +from pip._vendor.six.moves import configparser + +from pip._internal.exceptions import ( + ConfigurationError, ConfigurationFileCouldNotBeLoaded, +) +from pip._internal.locations import ( + legacy_config_file, new_config_file, running_under_virtualenv, + site_config_files, venv_config_file, +) +from pip._internal.utils.misc import ensure_dir, enum +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Any, Dict, Iterable, List, NewType, Optional, Tuple + ) + + RawConfigParser = configparser.RawConfigParser # Shorthand + Kind = NewType("Kind", str) + +logger = logging.getLogger(__name__) + + +# NOTE: Maybe use the optionx attribute to normalize keynames. +def _normalize_name(name): + # type: (str) -> str + """Make a name consistent regardless of source (environment or file) + """ + name = name.lower().replace('_', '-') + if name.startswith('--'): + name = name[2:] # only prefer long opts + return name + + +def _disassemble_key(name): + # type: (str) -> List[str] + return name.split(".", 1) + + +# The kinds of configurations there are. +kinds = enum( + USER="user", # User Specific + GLOBAL="global", # System Wide + VENV="venv", # Virtual Environment Specific + ENV="env", # from PIP_CONFIG_FILE + ENV_VAR="env-var", # from Environment Variables +) + + +class Configuration(object): + """Handles management of configuration. + + Provides an interface to accessing and managing configuration files. + + This class converts provides an API that takes "section.key-name" style + keys and stores the value associated with it as "key-name" under the + section "section". + + This allows for a clean interface wherein the both the section and the + key-name are preserved in an easy to manage form in the configuration files + and the data stored is also nice. + """ + + def __init__(self, isolated, load_only=None): + # type: (bool, Kind) -> None + super(Configuration, self).__init__() + + _valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.VENV, None] + if load_only not in _valid_load_only: + raise ConfigurationError( + "Got invalid value for load_only - should be one of {}".format( + ", ".join(map(repr, _valid_load_only[:-1])) + ) + ) + self.isolated = isolated # type: bool + self.load_only = load_only # type: Optional[Kind] + + # The order here determines the override order. + self._override_order = [ + kinds.GLOBAL, kinds.USER, kinds.VENV, kinds.ENV, kinds.ENV_VAR + ] + + self._ignore_env_names = ["version", "help"] + + # Because we keep track of where we got the data from + self._parsers = { + variant: [] for variant in self._override_order + } # type: Dict[Kind, List[Tuple[str, RawConfigParser]]] + self._config = { + variant: {} for variant in self._override_order + } # type: Dict[Kind, Dict[str, Any]] + self._modified_parsers = [] # type: List[Tuple[str, RawConfigParser]] + + def load(self): + # type: () -> None + """Loads configuration from configuration files and environment + """ + self._load_config_files() + if not self.isolated: + self._load_environment_vars() + + def get_file_to_edit(self): + # type: () -> Optional[str] + """Returns the file with highest priority in configuration + """ + assert self.load_only is not None, \ + "Need to be specified a file to be editing" + + try: + return self._get_parser_to_modify()[0] + except IndexError: + return None + + def items(self): + # type: () -> Iterable[Tuple[str, Any]] + """Returns key-value pairs like dict.items() representing the loaded + configuration + """ + return self._dictionary.items() + + def get_value(self, key): + # type: (str) -> Any + """Get a value from the configuration. + """ + try: + return self._dictionary[key] + except KeyError: + raise ConfigurationError("No such key - {}".format(key)) + + def set_value(self, key, value): + # type: (str, Any) -> None + """Modify a value in the configuration. + """ + self._ensure_have_load_only() + + fname, parser = self._get_parser_to_modify() + + if parser is not None: + section, name = _disassemble_key(key) + + # Modify the parser and the configuration + if not parser.has_section(section): + parser.add_section(section) + parser.set(section, name, value) + + self._config[self.load_only][key] = value + self._mark_as_modified(fname, parser) + + def unset_value(self, key): + # type: (str) -> None + """Unset a value in the configuration. + """ + self._ensure_have_load_only() + + if key not in self._config[self.load_only]: + raise ConfigurationError("No such key - {}".format(key)) + + fname, parser = self._get_parser_to_modify() + + if parser is not None: + section, name = _disassemble_key(key) + + # Remove the key in the parser + modified_something = False + if parser.has_section(section): + # Returns whether the option was removed or not + modified_something = parser.remove_option(section, name) + + if modified_something: + # name removed from parser, section may now be empty + section_iter = iter(parser.items(section)) + try: + val = six.next(section_iter) + except StopIteration: + val = None + + if val is None: + parser.remove_section(section) + + self._mark_as_modified(fname, parser) + else: + raise ConfigurationError( + "Fatal Internal error [id=1]. Please report as a bug." + ) + + del self._config[self.load_only][key] + + def save(self): + # type: () -> None + """Save the currentin-memory state. + """ + self._ensure_have_load_only() + + for fname, parser in self._modified_parsers: + logger.info("Writing to %s", fname) + + # Ensure directory exists. + ensure_dir(os.path.dirname(fname)) + + with open(fname, "w") as f: + parser.write(f) # type: ignore + + # + # Private routines + # + + def _ensure_have_load_only(self): + # type: () -> None + if self.load_only is None: + raise ConfigurationError("Needed a specific file to be modifying.") + logger.debug("Will be working with %s variant only", self.load_only) + + @property + def _dictionary(self): + # type: () -> Dict[str, Any] + """A dictionary representing the loaded configuration. + """ + # NOTE: Dictionaries are not populated if not loaded. So, conditionals + # are not needed here. + retval = {} + + for variant in self._override_order: + retval.update(self._config[variant]) + + return retval + + def _load_config_files(self): + # type: () -> None + """Loads configuration from configuration files + """ + config_files = dict(self._iter_config_files()) + if config_files[kinds.ENV][0:1] == [os.devnull]: + logger.debug( + "Skipping loading configuration files due to " + "environment's PIP_CONFIG_FILE being os.devnull" + ) + return + + for variant, files in config_files.items(): + for fname in files: + # If there's specific variant set in `load_only`, load only + # that variant, not the others. + if self.load_only is not None and variant != self.load_only: + logger.debug( + "Skipping file '%s' (variant: %s)", fname, variant + ) + continue + + parser = self._load_file(variant, fname) + + # Keeping track of the parsers used + self._parsers[variant].append((fname, parser)) + + def _load_file(self, variant, fname): + # type: (Kind, str) -> RawConfigParser + logger.debug("For variant '%s', will try loading '%s'", variant, fname) + parser = self._construct_parser(fname) + + for section in parser.sections(): + items = parser.items(section) + self._config[variant].update(self._normalized_keys(section, items)) + + return parser + + def _construct_parser(self, fname): + # type: (str) -> RawConfigParser + parser = configparser.RawConfigParser() + # If there is no such file, don't bother reading it but create the + # parser anyway, to hold the data. + # Doing this is useful when modifying and saving files, where we don't + # need to construct a parser. + if os.path.exists(fname): + try: + parser.read(fname) + except UnicodeDecodeError: + # See https://github.com/pypa/pip/issues/4963 + raise ConfigurationFileCouldNotBeLoaded( + reason="contains invalid {} characters".format( + locale.getpreferredencoding(False) + ), + fname=fname, + ) + except configparser.Error as error: + # See https://github.com/pypa/pip/issues/4893 + raise ConfigurationFileCouldNotBeLoaded(error=error) + return parser + + def _load_environment_vars(self): + # type: () -> None + """Loads configuration from environment variables + """ + self._config[kinds.ENV_VAR].update( + self._normalized_keys(":env:", self._get_environ_vars()) + ) + + def _normalized_keys(self, section, items): + # type: (str, Iterable[Tuple[str, Any]]) -> Dict[str, Any] + """Normalizes items to construct a dictionary with normalized keys. + + This routine is where the names become keys and are made the same + regardless of source - configuration files or environment. + """ + normalized = {} + for name, val in items: + key = section + "." + _normalize_name(name) + normalized[key] = val + return normalized + + def _get_environ_vars(self): + # type: () -> Iterable[Tuple[str, str]] + """Returns a generator with all environmental vars with prefix PIP_""" + for key, val in os.environ.items(): + should_be_yielded = ( + key.startswith("PIP_") and + key[4:].lower() not in self._ignore_env_names + ) + if should_be_yielded: + yield key[4:].lower(), val + + # XXX: This is patched in the tests. + def _iter_config_files(self): + # type: () -> Iterable[Tuple[Kind, List[str]]] + """Yields variant and configuration files associated with it. + + This should be treated like items of a dictionary. + """ + # SMELL: Move the conditions out of this function + + # environment variables have the lowest priority + config_file = os.environ.get('PIP_CONFIG_FILE', None) + if config_file is not None: + yield kinds.ENV, [config_file] + else: + yield kinds.ENV, [] + + # at the base we have any global configuration + yield kinds.GLOBAL, list(site_config_files) + + # per-user configuration next + should_load_user_config = not self.isolated and not ( + config_file and os.path.exists(config_file) + ) + if should_load_user_config: + # The legacy config file is overridden by the new config file + yield kinds.USER, [legacy_config_file, new_config_file] + + # finally virtualenv configuration first trumping others + if running_under_virtualenv(): + yield kinds.VENV, [venv_config_file] + + def _get_parser_to_modify(self): + # type: () -> Tuple[str, RawConfigParser] + # Determine which parser to modify + parsers = self._parsers[self.load_only] + if not parsers: + # This should not happen if everything works correctly. + raise ConfigurationError( + "Fatal Internal error [id=2]. Please report as a bug." + ) + + # Use the highest priority parser. + return parsers[-1] + + # XXX: This is patched in the tests. + def _mark_as_modified(self, fname, parser): + # type: (str, RawConfigParser) -> None + file_parser_tuple = (fname, parser) + if file_parser_tuple not in self._modified_parsers: + self._modified_parsers.append(file_parser_tuple) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/download.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/download.py new file mode 100644 index 0000000..2bbe176 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/download.py @@ -0,0 +1,971 @@ +from __future__ import absolute_import + +import cgi +import email.utils +import getpass +import json +import logging +import mimetypes +import os +import platform +import re +import shutil +import sys + +from pip._vendor import requests, six, urllib3 +from pip._vendor.cachecontrol import CacheControlAdapter +from pip._vendor.cachecontrol.caches import FileCache +from pip._vendor.lockfile import LockError +from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter +from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth +from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response +from pip._vendor.requests.structures import CaseInsensitiveDict +from pip._vendor.requests.utils import get_netrc_auth +# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is +# why we ignore the type on this import +from pip._vendor.six.moves import xmlrpc_client # type: ignore +from pip._vendor.six.moves.urllib import parse as urllib_parse +from pip._vendor.six.moves.urllib import request as urllib_request +from pip._vendor.urllib3.util import IS_PYOPENSSL + +import pip +from pip._internal.exceptions import HashMismatch, InstallationError +from pip._internal.locations import write_delete_marker_file +from pip._internal.models.index import PyPI +from pip._internal.utils.encoding import auto_decode +from pip._internal.utils.filesystem import check_path_owner +from pip._internal.utils.glibc import libc_ver +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + ARCHIVE_EXTENSIONS, ask_path_exists, backup_dir, call_subprocess, consume, + display_path, format_size, get_installed_version, rmtree, + split_auth_from_netloc, splitext, unpack_file, +) +from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.utils.ui import DownloadProgressProvider +from pip._internal.vcs import vcs + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Optional, Tuple, Dict, IO, Text, Union + ) + from pip._internal.models.link import Link # noqa: F401 + from pip._internal.utils.hashes import Hashes # noqa: F401 + from pip._internal.vcs import AuthInfo # noqa: F401 + +try: + import ssl # noqa +except ImportError: + ssl = None + +HAS_TLS = (ssl is not None) or IS_PYOPENSSL + +__all__ = ['get_file_content', + 'is_url', 'url_to_path', 'path_to_url', + 'is_archive_file', 'unpack_vcs_link', + 'unpack_file_url', 'is_vcs_url', 'is_file_url', + 'unpack_http_url', 'unpack_url'] + + +logger = logging.getLogger(__name__) + + +def user_agent(): + """ + Return a string representing the user agent. + """ + data = { + "installer": {"name": "pip", "version": pip.__version__}, + "python": platform.python_version(), + "implementation": { + "name": platform.python_implementation(), + }, + } + + if data["implementation"]["name"] == 'CPython': + data["implementation"]["version"] = platform.python_version() + elif data["implementation"]["name"] == 'PyPy': + if sys.pypy_version_info.releaselevel == 'final': + pypy_version_info = sys.pypy_version_info[:3] + else: + pypy_version_info = sys.pypy_version_info + data["implementation"]["version"] = ".".join( + [str(x) for x in pypy_version_info] + ) + elif data["implementation"]["name"] == 'Jython': + # Complete Guess + data["implementation"]["version"] = platform.python_version() + elif data["implementation"]["name"] == 'IronPython': + # Complete Guess + data["implementation"]["version"] = platform.python_version() + + if sys.platform.startswith("linux"): + from pip._vendor import distro + distro_infos = dict(filter( + lambda x: x[1], + zip(["name", "version", "id"], distro.linux_distribution()), + )) + libc = dict(filter( + lambda x: x[1], + zip(["lib", "version"], libc_ver()), + )) + if libc: + distro_infos["libc"] = libc + if distro_infos: + data["distro"] = distro_infos + + if sys.platform.startswith("darwin") and platform.mac_ver()[0]: + data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} + + if platform.system(): + data.setdefault("system", {})["name"] = platform.system() + + if platform.release(): + data.setdefault("system", {})["release"] = platform.release() + + if platform.machine(): + data["cpu"] = platform.machine() + + if HAS_TLS: + data["openssl_version"] = ssl.OPENSSL_VERSION + + setuptools_version = get_installed_version("setuptools") + if setuptools_version is not None: + data["setuptools_version"] = setuptools_version + + return "{data[installer][name]}/{data[installer][version]} {json}".format( + data=data, + json=json.dumps(data, separators=(",", ":"), sort_keys=True), + ) + + +class MultiDomainBasicAuth(AuthBase): + + def __init__(self, prompting=True): + # type: (bool) -> None + self.prompting = prompting + self.passwords = {} # type: Dict[str, AuthInfo] + + def __call__(self, req): + parsed = urllib_parse.urlparse(req.url) + + # Split the credentials from the netloc. + netloc, url_user_password = split_auth_from_netloc(parsed.netloc) + + # Set the url of the request to the url without any credentials + req.url = urllib_parse.urlunparse(parsed[:1] + (netloc,) + parsed[2:]) + + # Use any stored credentials that we have for this netloc + username, password = self.passwords.get(netloc, (None, None)) + + # Use the credentials embedded in the url if we have none stored + if username is None: + username, password = url_user_password + + # Get creds from netrc if we still don't have them + if username is None and password is None: + netrc_auth = get_netrc_auth(req.url) + username, password = netrc_auth if netrc_auth else (None, None) + + if username or password: + # Store the username and password + self.passwords[netloc] = (username, password) + + # Send the basic auth with this request + req = HTTPBasicAuth(username or "", password or "")(req) + + # Attach a hook to handle 401 responses + req.register_hook("response", self.handle_401) + + return req + + def handle_401(self, resp, **kwargs): + # We only care about 401 responses, anything else we want to just + # pass through the actual response + if resp.status_code != 401: + return resp + + # We are not able to prompt the user so simply return the response + if not self.prompting: + return resp + + parsed = urllib_parse.urlparse(resp.url) + + # Prompt the user for a new username and password + username = six.moves.input("User for %s: " % parsed.netloc) + password = getpass.getpass("Password: ") + + # Store the new username and password to use for future requests + if username or password: + self.passwords[parsed.netloc] = (username, password) + + # Consume content and release the original connection to allow our new + # request to reuse the same one. + resp.content + resp.raw.release_conn() + + # Add our new username and password to the request + req = HTTPBasicAuth(username or "", password or "")(resp.request) + req.register_hook("response", self.warn_on_401) + + # Send our new request + new_resp = resp.connection.send(req, **kwargs) + new_resp.history.append(resp) + + return new_resp + + def warn_on_401(self, resp, **kwargs): + # warn user that they provided incorrect credentials + if resp.status_code == 401: + logger.warning('401 Error, Credentials not correct for %s', + resp.request.url) + + +class LocalFSAdapter(BaseAdapter): + + def send(self, request, stream=None, timeout=None, verify=None, cert=None, + proxies=None): + pathname = url_to_path(request.url) + + resp = Response() + resp.status_code = 200 + resp.url = request.url + + try: + stats = os.stat(pathname) + except OSError as exc: + resp.status_code = 404 + resp.raw = exc + else: + modified = email.utils.formatdate(stats.st_mtime, usegmt=True) + content_type = mimetypes.guess_type(pathname)[0] or "text/plain" + resp.headers = CaseInsensitiveDict({ + "Content-Type": content_type, + "Content-Length": stats.st_size, + "Last-Modified": modified, + }) + + resp.raw = open(pathname, "rb") + resp.close = resp.raw.close + + return resp + + def close(self): + pass + + +class SafeFileCache(FileCache): + """ + A file based cache which is safe to use even when the target directory may + not be accessible or writable. + """ + + def __init__(self, *args, **kwargs): + super(SafeFileCache, self).__init__(*args, **kwargs) + + # Check to ensure that the directory containing our cache directory + # is owned by the user current executing pip. If it does not exist + # we will check the parent directory until we find one that does exist. + # If it is not owned by the user executing pip then we will disable + # the cache and log a warning. + if not check_path_owner(self.directory): + logger.warning( + "The directory '%s' or its parent directory is not owned by " + "the current user and the cache has been disabled. Please " + "check the permissions and owner of that directory. If " + "executing pip with sudo, you may want sudo's -H flag.", + self.directory, + ) + + # Set our directory to None to disable the Cache + self.directory = None + + def get(self, *args, **kwargs): + # If we don't have a directory, then the cache should be a no-op. + if self.directory is None: + return + + try: + return super(SafeFileCache, self).get(*args, **kwargs) + except (LockError, OSError, IOError): + # We intentionally silence this error, if we can't access the cache + # then we can just skip caching and process the request as if + # caching wasn't enabled. + pass + + def set(self, *args, **kwargs): + # If we don't have a directory, then the cache should be a no-op. + if self.directory is None: + return + + try: + return super(SafeFileCache, self).set(*args, **kwargs) + except (LockError, OSError, IOError): + # We intentionally silence this error, if we can't access the cache + # then we can just skip caching and process the request as if + # caching wasn't enabled. + pass + + def delete(self, *args, **kwargs): + # If we don't have a directory, then the cache should be a no-op. + if self.directory is None: + return + + try: + return super(SafeFileCache, self).delete(*args, **kwargs) + except (LockError, OSError, IOError): + # We intentionally silence this error, if we can't access the cache + # then we can just skip caching and process the request as if + # caching wasn't enabled. + pass + + +class InsecureHTTPAdapter(HTTPAdapter): + + def cert_verify(self, conn, url, verify, cert): + conn.cert_reqs = 'CERT_NONE' + conn.ca_certs = None + + +class PipSession(requests.Session): + + timeout = None # type: Optional[int] + + def __init__(self, *args, **kwargs): + retries = kwargs.pop("retries", 0) + cache = kwargs.pop("cache", None) + insecure_hosts = kwargs.pop("insecure_hosts", []) + + super(PipSession, self).__init__(*args, **kwargs) + + # Attach our User Agent to the request + self.headers["User-Agent"] = user_agent() + + # Attach our Authentication handler to the session + self.auth = MultiDomainBasicAuth() + + # Create our urllib3.Retry instance which will allow us to customize + # how we handle retries. + retries = urllib3.Retry( + # Set the total number of retries that a particular request can + # have. + total=retries, + + # A 503 error from PyPI typically means that the Fastly -> Origin + # connection got interrupted in some way. A 503 error in general + # is typically considered a transient error so we'll go ahead and + # retry it. + # A 500 may indicate transient error in Amazon S3 + # A 520 or 527 - may indicate transient error in CloudFlare + status_forcelist=[500, 503, 520, 527], + + # Add a small amount of back off between failed requests in + # order to prevent hammering the service. + backoff_factor=0.25, + ) + + # We want to _only_ cache responses on securely fetched origins. We do + # this because we can't validate the response of an insecurely fetched + # origin, and we don't want someone to be able to poison the cache and + # require manual eviction from the cache to fix it. + if cache: + secure_adapter = CacheControlAdapter( + cache=SafeFileCache(cache, use_dir_lock=True), + max_retries=retries, + ) + else: + secure_adapter = HTTPAdapter(max_retries=retries) + + # Our Insecure HTTPAdapter disables HTTPS validation. It does not + # support caching (see above) so we'll use it for all http:// URLs as + # well as any https:// host that we've marked as ignoring TLS errors + # for. + insecure_adapter = InsecureHTTPAdapter(max_retries=retries) + + self.mount("https://", secure_adapter) + self.mount("http://", insecure_adapter) + + # Enable file:// urls + self.mount("file://", LocalFSAdapter()) + + # We want to use a non-validating adapter for any requests which are + # deemed insecure. + for host in insecure_hosts: + self.mount("https://{}/".format(host), insecure_adapter) + + def request(self, method, url, *args, **kwargs): + # Allow setting a default timeout on a session + kwargs.setdefault("timeout", self.timeout) + + # Dispatch the actual request + return super(PipSession, self).request(method, url, *args, **kwargs) + + +def get_file_content(url, comes_from=None, session=None): + # type: (str, Optional[str], Optional[PipSession]) -> Tuple[str, Text] + """Gets the content of a file; it may be a filename, file: URL, or + http: URL. Returns (location, content). Content is unicode. + + :param url: File path or url. + :param comes_from: Origin description of requirements. + :param session: Instance of pip.download.PipSession. + """ + if session is None: + raise TypeError( + "get_file_content() missing 1 required keyword argument: 'session'" + ) + + match = _scheme_re.search(url) + if match: + scheme = match.group(1).lower() + if (scheme == 'file' and comes_from and + comes_from.startswith('http')): + raise InstallationError( + 'Requirements file %s references URL %s, which is local' + % (comes_from, url)) + if scheme == 'file': + path = url.split(':', 1)[1] + path = path.replace('\\', '/') + match = _url_slash_drive_re.match(path) + if match: + path = match.group(1) + ':' + path.split('|', 1)[1] + path = urllib_parse.unquote(path) + if path.startswith('/'): + path = '/' + path.lstrip('/') + url = path + else: + # FIXME: catch some errors + resp = session.get(url) + resp.raise_for_status() + return resp.url, resp.text + try: + with open(url, 'rb') as f: + content = auto_decode(f.read()) + except IOError as exc: + raise InstallationError( + 'Could not open requirements file: %s' % str(exc) + ) + return url, content + + +_scheme_re = re.compile(r'^(http|https|file):', re.I) +_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I) + + +def is_url(name): + # type: (Union[str, Text]) -> bool + """Returns true if the name looks like a URL""" + if ':' not in name: + return False + scheme = name.split(':', 1)[0].lower() + return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes + + +def url_to_path(url): + # type: (str) -> str + """ + Convert a file: URL to a path. + """ + assert url.startswith('file:'), ( + "You can only turn file: urls into filenames (not %r)" % url) + + _, netloc, path, _, _ = urllib_parse.urlsplit(url) + + # if we have a UNC path, prepend UNC share notation + if netloc: + netloc = '\\\\' + netloc + + path = urllib_request.url2pathname(netloc + path) + return path + + +def path_to_url(path): + # type: (Union[str, Text]) -> str + """ + Convert a path to a file: URL. The path will be made absolute and have + quoted path parts. + """ + path = os.path.normpath(os.path.abspath(path)) + url = urllib_parse.urljoin('file:', urllib_request.pathname2url(path)) + return url + + +def is_archive_file(name): + # type: (str) -> bool + """Return True if `name` is a considered as an archive file.""" + ext = splitext(name)[1].lower() + if ext in ARCHIVE_EXTENSIONS: + return True + return False + + +def unpack_vcs_link(link, location): + vcs_backend = _get_used_vcs_backend(link) + vcs_backend.unpack(location) + + +def _get_used_vcs_backend(link): + for backend in vcs.backends: + if link.scheme in backend.schemes: + vcs_backend = backend(link.url) + return vcs_backend + + +def is_vcs_url(link): + # type: (Link) -> bool + return bool(_get_used_vcs_backend(link)) + + +def is_file_url(link): + # type: (Link) -> bool + return link.url.lower().startswith('file:') + + +def is_dir_url(link): + # type: (Link) -> bool + """Return whether a file:// Link points to a directory. + + ``link`` must not have any other scheme but file://. Call is_file_url() + first. + + """ + link_path = url_to_path(link.url_without_fragment) + return os.path.isdir(link_path) + + +def _progress_indicator(iterable, *args, **kwargs): + return iterable + + +def _download_url( + resp, # type: Response + link, # type: Link + content_file, # type: IO + hashes, # type: Hashes + progress_bar # type: str +): + # type: (...) -> None + try: + total_length = int(resp.headers['content-length']) + except (ValueError, KeyError, TypeError): + total_length = 0 + + cached_resp = getattr(resp, "from_cache", False) + if logger.getEffectiveLevel() > logging.INFO: + show_progress = False + elif cached_resp: + show_progress = False + elif total_length > (40 * 1000): + show_progress = True + elif not total_length: + show_progress = True + else: + show_progress = False + + show_url = link.show_url + + def resp_read(chunk_size): + try: + # Special case for urllib3. + for chunk in resp.raw.stream( + chunk_size, + # We use decode_content=False here because we don't + # want urllib3 to mess with the raw bytes we get + # from the server. If we decompress inside of + # urllib3 then we cannot verify the checksum + # because the checksum will be of the compressed + # file. This breakage will only occur if the + # server adds a Content-Encoding header, which + # depends on how the server was configured: + # - Some servers will notice that the file isn't a + # compressible file and will leave the file alone + # and with an empty Content-Encoding + # - Some servers will notice that the file is + # already compressed and will leave the file + # alone and will add a Content-Encoding: gzip + # header + # - Some servers won't notice anything at all and + # will take a file that's already been compressed + # and compress it again and set the + # Content-Encoding: gzip header + # + # By setting this not to decode automatically we + # hope to eliminate problems with the second case. + decode_content=False): + yield chunk + except AttributeError: + # Standard file-like object. + while True: + chunk = resp.raw.read(chunk_size) + if not chunk: + break + yield chunk + + def written_chunks(chunks): + for chunk in chunks: + content_file.write(chunk) + yield chunk + + progress_indicator = _progress_indicator + + if link.netloc == PyPI.netloc: + url = show_url + else: + url = link.url_without_fragment + + if show_progress: # We don't show progress on cached responses + progress_indicator = DownloadProgressProvider(progress_bar, + max=total_length) + if total_length: + logger.info("Downloading %s (%s)", url, format_size(total_length)) + else: + logger.info("Downloading %s", url) + elif cached_resp: + logger.info("Using cached %s", url) + else: + logger.info("Downloading %s", url) + + logger.debug('Downloading from URL %s', link) + + downloaded_chunks = written_chunks( + progress_indicator( + resp_read(CONTENT_CHUNK_SIZE), + CONTENT_CHUNK_SIZE + ) + ) + if hashes: + hashes.check_against_chunks(downloaded_chunks) + else: + consume(downloaded_chunks) + + +def _copy_file(filename, location, link): + copy = True + download_location = os.path.join(location, link.filename) + if os.path.exists(download_location): + response = ask_path_exists( + 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)abort' % + display_path(download_location), ('i', 'w', 'b', 'a')) + if response == 'i': + copy = False + elif response == 'w': + logger.warning('Deleting %s', display_path(download_location)) + os.remove(download_location) + elif response == 'b': + dest_file = backup_dir(download_location) + logger.warning( + 'Backing up %s to %s', + display_path(download_location), + display_path(dest_file), + ) + shutil.move(download_location, dest_file) + elif response == 'a': + sys.exit(-1) + if copy: + shutil.copy(filename, download_location) + logger.info('Saved %s', display_path(download_location)) + + +def unpack_http_url( + link, # type: Link + location, # type: str + download_dir=None, # type: Optional[str] + session=None, # type: Optional[PipSession] + hashes=None, # type: Optional[Hashes] + progress_bar="on" # type: str +): + # type: (...) -> None + if session is None: + raise TypeError( + "unpack_http_url() missing 1 required keyword argument: 'session'" + ) + + with TempDirectory(kind="unpack") as temp_dir: + # If a download dir is specified, is the file already downloaded there? + already_downloaded_path = None + if download_dir: + already_downloaded_path = _check_download_dir(link, + download_dir, + hashes) + + if already_downloaded_path: + from_path = already_downloaded_path + content_type = mimetypes.guess_type(from_path)[0] + else: + # let's download to a tmp dir + from_path, content_type = _download_http_url(link, + session, + temp_dir.path, + hashes, + progress_bar) + + # unpack the archive to the build dir location. even when only + # downloading archives, they have to be unpacked to parse dependencies + unpack_file(from_path, location, content_type, link) + + # a download dir is specified; let's copy the archive there + if download_dir and not already_downloaded_path: + _copy_file(from_path, download_dir, link) + + if not already_downloaded_path: + os.unlink(from_path) + + +def unpack_file_url( + link, # type: Link + location, # type: str + download_dir=None, # type: Optional[str] + hashes=None # type: Optional[Hashes] +): + # type: (...) -> None + """Unpack link into location. + + If download_dir is provided and link points to a file, make a copy + of the link file inside download_dir. + """ + link_path = url_to_path(link.url_without_fragment) + + # If it's a url to a local directory + if is_dir_url(link): + if os.path.isdir(location): + rmtree(location) + shutil.copytree(link_path, location, symlinks=True) + if download_dir: + logger.info('Link is a directory, ignoring download_dir') + return + + # If --require-hashes is off, `hashes` is either empty, the + # link's embedded hash, or MissingHashes; it is required to + # match. If --require-hashes is on, we are satisfied by any + # hash in `hashes` matching: a URL-based or an option-based + # one; no internet-sourced hash will be in `hashes`. + if hashes: + hashes.check_against_path(link_path) + + # If a download dir is specified, is the file already there and valid? + already_downloaded_path = None + if download_dir: + already_downloaded_path = _check_download_dir(link, + download_dir, + hashes) + + if already_downloaded_path: + from_path = already_downloaded_path + else: + from_path = link_path + + content_type = mimetypes.guess_type(from_path)[0] + + # unpack the archive to the build dir location. even when only downloading + # archives, they have to be unpacked to parse dependencies + unpack_file(from_path, location, content_type, link) + + # a download dir is specified and not already downloaded + if download_dir and not already_downloaded_path: + _copy_file(from_path, download_dir, link) + + +def _copy_dist_from_dir(link_path, location): + """Copy distribution files in `link_path` to `location`. + + Invoked when user requests to install a local directory. E.g.: + + pip install . + pip install ~/dev/git-repos/python-prompt-toolkit + + """ + + # Note: This is currently VERY SLOW if you have a lot of data in the + # directory, because it copies everything with `shutil.copytree`. + # What it should really do is build an sdist and install that. + # See https://github.com/pypa/pip/issues/2195 + + if os.path.isdir(location): + rmtree(location) + + # build an sdist + setup_py = 'setup.py' + sdist_args = [sys.executable] + sdist_args.append('-c') + sdist_args.append(SETUPTOOLS_SHIM % setup_py) + sdist_args.append('sdist') + sdist_args += ['--dist-dir', location] + logger.info('Running setup.py sdist for %s', link_path) + + with indent_log(): + call_subprocess(sdist_args, cwd=link_path, show_stdout=False) + + # unpack sdist into `location` + sdist = os.path.join(location, os.listdir(location)[0]) + logger.info('Unpacking sdist %s into %s', sdist, location) + unpack_file(sdist, location, content_type=None, link=None) + + +class PipXmlrpcTransport(xmlrpc_client.Transport): + """Provide a `xmlrpclib.Transport` implementation via a `PipSession` + object. + """ + + def __init__(self, index_url, session, use_datetime=False): + xmlrpc_client.Transport.__init__(self, use_datetime) + index_parts = urllib_parse.urlparse(index_url) + self._scheme = index_parts.scheme + self._session = session + + def request(self, host, handler, request_body, verbose=False): + parts = (self._scheme, host, handler, None, None, None) + url = urllib_parse.urlunparse(parts) + try: + headers = {'Content-Type': 'text/xml'} + response = self._session.post(url, data=request_body, + headers=headers, stream=True) + response.raise_for_status() + self.verbose = verbose + return self.parse_response(response.raw) + except requests.HTTPError as exc: + logger.critical( + "HTTP error %s while getting %s", + exc.response.status_code, url, + ) + raise + + +def unpack_url( + link, # type: Optional[Link] + location, # type: Optional[str] + download_dir=None, # type: Optional[str] + only_download=False, # type: bool + session=None, # type: Optional[PipSession] + hashes=None, # type: Optional[Hashes] + progress_bar="on" # type: str +): + # type: (...) -> None + """Unpack link. + If link is a VCS link: + if only_download, export into download_dir and ignore location + else unpack into location + for other types of link: + - unpack into location + - if download_dir, copy the file into download_dir + - if only_download, mark location for deletion + + :param hashes: A Hashes object, one of whose embedded hashes must match, + or HashMismatch will be raised. If the Hashes is empty, no matches are + required, and unhashable types of requirements (like VCS ones, which + would ordinarily raise HashUnsupported) are allowed. + """ + # non-editable vcs urls + if is_vcs_url(link): + unpack_vcs_link(link, location) + + # file urls + elif is_file_url(link): + unpack_file_url(link, location, download_dir, hashes=hashes) + + # http urls + else: + if session is None: + session = PipSession() + + unpack_http_url( + link, + location, + download_dir, + session, + hashes=hashes, + progress_bar=progress_bar + ) + if only_download: + write_delete_marker_file(location) + + +def _download_http_url( + link, # type: Link + session, # type: PipSession + temp_dir, # type: str + hashes, # type: Hashes + progress_bar # type: str +): + # type: (...) -> Tuple[str, str] + """Download link url into temp_dir using provided session""" + target_url = link.url.split('#', 1)[0] + try: + resp = session.get( + target_url, + # We use Accept-Encoding: identity here because requests + # defaults to accepting compressed responses. This breaks in + # a variety of ways depending on how the server is configured. + # - Some servers will notice that the file isn't a compressible + # file and will leave the file alone and with an empty + # Content-Encoding + # - Some servers will notice that the file is already + # compressed and will leave the file alone and will add a + # Content-Encoding: gzip header + # - Some servers won't notice anything at all and will take + # a file that's already been compressed and compress it again + # and set the Content-Encoding: gzip header + # By setting this to request only the identity encoding We're + # hoping to eliminate the third case. Hopefully there does not + # exist a server which when given a file will notice it is + # already compressed and that you're not asking for a + # compressed file and will then decompress it before sending + # because if that's the case I don't think it'll ever be + # possible to make this work. + headers={"Accept-Encoding": "identity"}, + stream=True, + ) + resp.raise_for_status() + except requests.HTTPError as exc: + logger.critical( + "HTTP error %s while getting %s", exc.response.status_code, link, + ) + raise + + content_type = resp.headers.get('content-type', '') + filename = link.filename # fallback + # Have a look at the Content-Disposition header for a better guess + content_disposition = resp.headers.get('content-disposition') + if content_disposition: + type, params = cgi.parse_header(content_disposition) + # We use ``or`` here because we don't want to use an "empty" value + # from the filename param. + filename = params.get('filename') or filename + ext = splitext(filename)[1] + if not ext: + ext = mimetypes.guess_extension(content_type) + if ext: + filename += ext + if not ext and link.url != resp.url: + ext = os.path.splitext(resp.url)[1] + if ext: + filename += ext + file_path = os.path.join(temp_dir, filename) + with open(file_path, 'wb') as content_file: + _download_url(resp, link, content_file, hashes, progress_bar) + return file_path, content_type + + +def _check_download_dir(link, download_dir, hashes): + # type: (Link, str, Hashes) -> Optional[str] + """ Check download_dir for previously downloaded file with correct hash + If a correct file is found return its path else None + """ + download_path = os.path.join(download_dir, link.filename) + if os.path.exists(download_path): + # If already downloaded, does its hash match? + logger.info('File was already downloaded %s', download_path) + if hashes: + try: + hashes.check_against_path(download_path) + except HashMismatch: + logger.warning( + 'Previously-downloaded file %s has bad hash. ' + 'Re-downloading.', + download_path + ) + os.unlink(download_path) + return None + return download_path + return None diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/exceptions.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/exceptions.py new file mode 100644 index 0000000..38ceeea --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/exceptions.py @@ -0,0 +1,274 @@ +"""Exceptions used throughout package""" +from __future__ import absolute_import + +from itertools import chain, groupby, repeat + +from pip._vendor.six import iteritems + +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional # noqa: F401 + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + + +class PipError(Exception): + """Base pip exception""" + + +class ConfigurationError(PipError): + """General exception in configuration""" + + +class InstallationError(PipError): + """General exception during installation""" + + +class UninstallationError(PipError): + """General exception during uninstallation""" + + +class DistributionNotFound(InstallationError): + """Raised when a distribution cannot be found to satisfy a requirement""" + + +class RequirementsFileParseError(InstallationError): + """Raised when a general error occurs parsing a requirements file line.""" + + +class BestVersionAlreadyInstalled(PipError): + """Raised when the most up-to-date version of a package is already + installed.""" + + +class BadCommand(PipError): + """Raised when virtualenv or a command is not found""" + + +class CommandError(PipError): + """Raised when there is an error in command-line arguments""" + + +class PreviousBuildDirError(PipError): + """Raised when there's a previous conflicting build directory""" + + +class InvalidWheelFilename(InstallationError): + """Invalid wheel filename.""" + + +class UnsupportedWheel(InstallationError): + """Unsupported wheel.""" + + +class HashErrors(InstallationError): + """Multiple HashError instances rolled into one for reporting""" + + def __init__(self): + self.errors = [] + + def append(self, error): + self.errors.append(error) + + def __str__(self): + lines = [] + self.errors.sort(key=lambda e: e.order) + for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__): + lines.append(cls.head) + lines.extend(e.body() for e in errors_of_cls) + if lines: + return '\n'.join(lines) + + def __nonzero__(self): + return bool(self.errors) + + def __bool__(self): + return self.__nonzero__() + + +class HashError(InstallationError): + """ + A failure to verify a package against known-good hashes + + :cvar order: An int sorting hash exception classes by difficulty of + recovery (lower being harder), so the user doesn't bother fretting + about unpinned packages when he has deeper issues, like VCS + dependencies, to deal with. Also keeps error reports in a + deterministic order. + :cvar head: A section heading for display above potentially many + exceptions of this kind + :ivar req: The InstallRequirement that triggered this error. This is + pasted on after the exception is instantiated, because it's not + typically available earlier. + + """ + req = None # type: Optional[InstallRequirement] + head = '' + + def body(self): + """Return a summary of me for display under the heading. + + This default implementation simply prints a description of the + triggering requirement. + + :param req: The InstallRequirement that provoked this error, with + populate_link() having already been called + + """ + return ' %s' % self._requirement_name() + + def __str__(self): + return '%s\n%s' % (self.head, self.body()) + + def _requirement_name(self): + """Return a description of the requirement that triggered me. + + This default implementation returns long description of the req, with + line numbers + + """ + return str(self.req) if self.req else 'unknown package' + + +class VcsHashUnsupported(HashError): + """A hash was provided for a version-control-system-based requirement, but + we don't have a method for hashing those.""" + + order = 0 + head = ("Can't verify hashes for these requirements because we don't " + "have a way to hash version control repositories:") + + +class DirectoryUrlHashUnsupported(HashError): + """A hash was provided for a version-control-system-based requirement, but + we don't have a method for hashing those.""" + + order = 1 + head = ("Can't verify hashes for these file:// requirements because they " + "point to directories:") + + +class HashMissing(HashError): + """A hash was needed for a requirement but is absent.""" + + order = 2 + head = ('Hashes are required in --require-hashes mode, but they are ' + 'missing from some requirements. Here is a list of those ' + 'requirements along with the hashes their downloaded archives ' + 'actually had. Add lines like these to your requirements files to ' + 'prevent tampering. (If you did not enable --require-hashes ' + 'manually, note that it turns on automatically when any package ' + 'has a hash.)') + + def __init__(self, gotten_hash): + """ + :param gotten_hash: The hash of the (possibly malicious) archive we + just downloaded + """ + self.gotten_hash = gotten_hash + + def body(self): + # Dodge circular import. + from pip._internal.utils.hashes import FAVORITE_HASH + + package = None + if self.req: + # In the case of URL-based requirements, display the original URL + # seen in the requirements file rather than the package name, + # so the output can be directly copied into the requirements file. + package = (self.req.original_link if self.req.original_link + # In case someone feeds something downright stupid + # to InstallRequirement's constructor. + else getattr(self.req, 'req', None)) + return ' %s --hash=%s:%s' % (package or 'unknown package', + FAVORITE_HASH, + self.gotten_hash) + + +class HashUnpinned(HashError): + """A requirement had a hash specified but was not pinned to a specific + version.""" + + order = 3 + head = ('In --require-hashes mode, all requirements must have their ' + 'versions pinned with ==. These do not:') + + +class HashMismatch(HashError): + """ + Distribution file hash values don't match. + + :ivar package_name: The name of the package that triggered the hash + mismatch. Feel free to write to this after the exception is raise to + improve its error message. + + """ + order = 4 + head = ('THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS ' + 'FILE. If you have updated the package versions, please update ' + 'the hashes. Otherwise, examine the package contents carefully; ' + 'someone may have tampered with them.') + + def __init__(self, allowed, gots): + """ + :param allowed: A dict of algorithm names pointing to lists of allowed + hex digests + :param gots: A dict of algorithm names pointing to hashes we + actually got from the files under suspicion + """ + self.allowed = allowed + self.gots = gots + + def body(self): + return ' %s:\n%s' % (self._requirement_name(), + self._hash_comparison()) + + def _hash_comparison(self): + """ + Return a comparison of actual and expected hash values. + + Example:: + + Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde + or 123451234512345123451234512345123451234512345 + Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef + + """ + def hash_then_or(hash_name): + # For now, all the decent hashes have 6-char names, so we can get + # away with hard-coding space literals. + return chain([hash_name], repeat(' or')) + + lines = [] + for hash_name, expecteds in iteritems(self.allowed): + prefix = hash_then_or(hash_name) + lines.extend((' Expected %s %s' % (next(prefix), e)) + for e in expecteds) + lines.append(' Got %s\n' % + self.gots[hash_name].hexdigest()) + prefix = ' or' + return '\n'.join(lines) + + +class UnsupportedPythonVersion(InstallationError): + """Unsupported python version according to Requires-Python package + metadata.""" + + +class ConfigurationFileCouldNotBeLoaded(ConfigurationError): + """When there are errors while loading a configuration file + """ + + def __init__(self, reason="could not be loaded", fname=None, error=None): + super(ConfigurationFileCouldNotBeLoaded, self).__init__(error) + self.reason = reason + self.fname = fname + self.error = error + + def __str__(self): + if self.fname is not None: + message_part = " in {}.".format(self.fname) + else: + assert self.error is not None + message_part = ".\n{}\n".format(self.error.message) + return "Configuration file {}{}".format(self.reason, message_part) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/index.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/index.py new file mode 100644 index 0000000..9eda3a3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/index.py @@ -0,0 +1,990 @@ +"""Routines related to PyPI, indexes""" +from __future__ import absolute_import + +import cgi +import itertools +import logging +import mimetypes +import os +import posixpath +import re +import sys +from collections import namedtuple + +from pip._vendor import html5lib, requests, six +from pip._vendor.distlib.compat import unescape +from pip._vendor.packaging import specifiers +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import parse as parse_version +from pip._vendor.requests.exceptions import RetryError, SSLError +from pip._vendor.six.moves.urllib import parse as urllib_parse +from pip._vendor.six.moves.urllib import request as urllib_request + +from pip._internal.download import HAS_TLS, is_url, path_to_url, url_to_path +from pip._internal.exceptions import ( + BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename, + UnsupportedWheel, +) +from pip._internal.models.candidate import InstallationCandidate +from pip._internal.models.format_control import FormatControl +from pip._internal.models.index import PyPI +from pip._internal.models.link import Link +from pip._internal.pep425tags import get_supported +from pip._internal.utils.compat import ipaddress +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, WHEEL_EXTENSION, normalize_path, + redact_password_from_url, +) +from pip._internal.utils.packaging import check_requires_python +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.wheel import Wheel + +if MYPY_CHECK_RUNNING: + from logging import Logger # noqa: F401 + from typing import ( # noqa: F401 + Tuple, Optional, Any, List, Union, Callable, Set, Sequence, + Iterable, MutableMapping + ) + from pip._vendor.packaging.version import _BaseVersion # noqa: F401 + from pip._vendor.requests import Response # noqa: F401 + from pip._internal.req import InstallRequirement # noqa: F401 + from pip._internal.download import PipSession # noqa: F401 + + SecureOrigin = Tuple[str, str, Optional[str]] + BuildTag = Tuple[Any, ...] # either emply tuple or Tuple[int, str] + CandidateSortingKey = Tuple[int, _BaseVersion, BuildTag, Optional[int]] + +__all__ = ['FormatControl', 'PackageFinder'] + + +SECURE_ORIGINS = [ + # protocol, hostname, port + # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC) + ("https", "*", "*"), + ("*", "localhost", "*"), + ("*", "127.0.0.0/8", "*"), + ("*", "::1/128", "*"), + ("file", "*", None), + # ssh is always secure. + ("ssh", "*", "*"), +] # type: List[SecureOrigin] + + +logger = logging.getLogger(__name__) + + +def _match_vcs_scheme(url): + # type: (str) -> Optional[str] + """Look for VCS schemes in the URL. + + Returns the matched VCS scheme, or None if there's no match. + """ + from pip._internal.vcs import VcsSupport + for scheme in VcsSupport.schemes: + if url.lower().startswith(scheme) and url[len(scheme)] in '+:': + return scheme + return None + + +def _is_url_like_archive(url): + # type: (str) -> bool + """Return whether the URL looks like an archive. + """ + filename = Link(url).filename + for bad_ext in ARCHIVE_EXTENSIONS: + if filename.endswith(bad_ext): + return True + return False + + +class _NotHTML(Exception): + def __init__(self, content_type, request_desc): + # type: (str, str) -> None + super(_NotHTML, self).__init__(content_type, request_desc) + self.content_type = content_type + self.request_desc = request_desc + + +def _ensure_html_header(response): + # type: (Response) -> None + """Check the Content-Type header to ensure the response contains HTML. + + Raises `_NotHTML` if the content type is not text/html. + """ + content_type = response.headers.get("Content-Type", "") + if not content_type.lower().startswith("text/html"): + raise _NotHTML(content_type, response.request.method) + + +class _NotHTTP(Exception): + pass + + +def _ensure_html_response(url, session): + # type: (str, PipSession) -> None + """Send a HEAD request to the URL, and ensure the response contains HTML. + + Raises `_NotHTTP` if the URL is not available for a HEAD request, or + `_NotHTML` if the content type is not text/html. + """ + scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url) + if scheme not in {'http', 'https'}: + raise _NotHTTP() + + resp = session.head(url, allow_redirects=True) + resp.raise_for_status() + + _ensure_html_header(resp) + + +def _get_html_response(url, session): + # type: (str, PipSession) -> Response + """Access an HTML page 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. + 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. + """ + if _is_url_like_archive(url): + _ensure_html_response(url, session=session) + + logger.debug('Getting page %s', url) + + resp = session.get( + url, + headers={ + "Accept": "text/html", + # We don't want to blindly returned cached data for + # /simple/, because authors generally expecting that + # twine upload && pip install will function, but if + # they've done a pip install in the last ~10 minutes + # it won't. Thus by setting this to zero we will not + # blindly use any cached data, however the benefit of + # using max-age=0 instead of no-cache, is that we will + # still support conditional requests, so we will still + # minimize traffic sent in cases where the page hasn't + # changed at all, we will just always incur the round + # trip for the conditional GET now instead of only + # once per 10 minutes. + # For more information, please see pypa/pip#5670. + "Cache-Control": "max-age=0", + }, + ) + resp.raise_for_status() + + # 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) + + return resp + + +def _handle_get_page_fail( + link, # type: Link + reason, # type: Union[str, Exception] + meth=None # type: Optional[Callable[..., None]] +): + # type: (...) -> None + if meth is None: + meth = logger.debug + meth("Could not fetch URL %s: %s - skipping", link, reason) + + +def _get_html_page(link, session=None): + # type: (Link, Optional[PipSession]) -> Optional[HTMLPage] + if session is None: + raise TypeError( + "_get_html_page() missing 1 required keyword argument: 'session'" + ) + + url = link.url.split('#', 1)[0] + + # Check for VCS schemes that do not support lookup as web pages. + vcs_scheme = _match_vcs_scheme(url) + if vcs_scheme: + logger.debug('Cannot look at %s URL %s', vcs_scheme, link) + return None + + # Tack index.html onto file:// URLs that point to directories + scheme, _, path, _, _, _ = urllib_parse.urlparse(url) + if (scheme == 'file' and os.path.isdir(urllib_request.url2pathname(path))): + # add trailing slash if not present so urljoin doesn't trim + # final segment + if not url.endswith('/'): + url += '/' + url = urllib_parse.urljoin(url, 'index.html') + logger.debug(' file: URL is directory, getting %s', url) + + try: + resp = _get_html_response(url, session=session) + except _NotHTTP as exc: + logger.debug( + 'Skipping page %s because it looks like an archive, and cannot ' + 'be checked by HEAD.', link, + ) + except _NotHTML as exc: + logger.debug( + 'Skipping page %s because the %s request got Content-Type: %s', + link, exc.request_desc, exc.content_type, + ) + except requests.HTTPError as exc: + _handle_get_page_fail(link, exc) + except RetryError as exc: + _handle_get_page_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) + except requests.ConnectionError as exc: + _handle_get_page_fail(link, "connection error: %s" % exc) + except requests.Timeout: + _handle_get_page_fail(link, "timed out") + else: + return HTMLPage(resp.content, resp.url, resp.headers) + return None + + +class PackageFinder(object): + """This finds packages. + + This is meant to match easy_install's technique for looking for + packages, by reading pages and looking for appropriate links. + """ + + def __init__( + self, + find_links, # type: List[str] + index_urls, # type: List[str] + allow_all_prereleases=False, # type: bool + trusted_hosts=None, # type: Optional[Iterable[str]] + session=None, # type: Optional[PipSession] + format_control=None, # type: Optional[FormatControl] + platform=None, # type: Optional[str] + versions=None, # type: Optional[List[str]] + abi=None, # type: Optional[str] + implementation=None, # type: Optional[str] + prefer_binary=False # type: bool + ): + # type: (...) -> None + """Create a PackageFinder. + + :param format_control: A FormatControl object or None. Used to control + the selection of source packages / binary packages when consulting + the index and links. + :param platform: A string or None. If None, searches for packages + that are supported by the current system. Otherwise, will find + packages that can be built on the platform passed in. These + packages will only be downloaded for distribution: they will + not be built locally. + :param versions: A list of strings or None. This is passed directly + to pep425tags.py in the get_supported() method. + :param abi: A string or None. This is passed directly + to pep425tags.py in the get_supported() method. + :param implementation: A string or None. This is passed directly + to pep425tags.py in the get_supported() method. + """ + if session is None: + raise TypeError( + "PackageFinder() missing 1 required keyword argument: " + "'session'" + ) + + # Build find_links. If an argument starts with ~, it may be + # a local file relative to a home directory. So try normalizing + # it and if it exists, use the normalized version. + # This is deliberately conservative - it might be fine just to + # blindly normalize anything starting with a ~... + self.find_links = [] # type: List[str] + for link in find_links: + if link.startswith('~'): + new_link = normalize_path(link) + if os.path.exists(new_link): + link = new_link + self.find_links.append(link) + + self.index_urls = index_urls + + # These are boring links that have already been logged somehow: + self.logged_links = set() # type: Set[Link] + + self.format_control = format_control or FormatControl(set(), set()) + + # Domains that we won't emit warnings for when not using HTTPS + self.secure_origins = [ + ("*", host, "*") + for host in (trusted_hosts if trusted_hosts else []) + ] # type: List[SecureOrigin] + + # Do we want to allow _all_ pre-releases? + self.allow_all_prereleases = allow_all_prereleases + + # The Session we'll use to make requests + self.session = session + + # The valid tags to check potential found wheel candidates against + self.valid_tags = get_supported( + versions=versions, + platform=platform, + abi=abi, + impl=implementation, + ) + + # Do we prefer old, but valid, binary dist over new source dist + self.prefer_binary = prefer_binary + + # If we don't have TLS enabled, then WARN if anyplace we're looking + # relies on TLS. + if not HAS_TLS: + for link in itertools.chain(self.index_urls, self.find_links): + parsed = urllib_parse.urlparse(link) + if parsed.scheme == "https": + logger.warning( + "pip is configured with locations that require " + "TLS/SSL, however the ssl module in Python is not " + "available." + ) + break + + def get_formatted_locations(self): + # type: () -> str + lines = [] + if self.index_urls and self.index_urls != [PyPI.simple_url]: + lines.append( + "Looking in indexes: {}".format(", ".join( + redact_password_from_url(url) for url in self.index_urls)) + ) + if self.find_links: + lines.append( + "Looking in links: {}".format(", ".join(self.find_links)) + ) + return "\n".join(lines) + + @staticmethod + def _sort_locations(locations, expand_dir=False): + # type: (Sequence[str], bool) -> Tuple[List[str], List[str]] + """ + Sort locations into "files" (archives) and "urls", and return + a pair of lists (files,urls) + """ + files = [] + urls = [] + + # puts the url for the given file path into the appropriate list + def sort_path(path): + url = path_to_url(path) + if mimetypes.guess_type(url, strict=False)[0] == 'text/html': + urls.append(url) + else: + files.append(url) + + for url in locations: + + is_local_path = os.path.exists(url) + is_file_url = url.startswith('file:') + + if is_local_path or is_file_url: + if is_local_path: + path = url + else: + path = url_to_path(url) + if os.path.isdir(path): + if expand_dir: + path = os.path.realpath(path) + for item in os.listdir(path): + sort_path(os.path.join(path, item)) + elif is_file_url: + urls.append(url) + else: + logger.warning( + "Path '{0}' is ignored: " + "it is a directory.".format(path), + ) + elif os.path.isfile(path): + sort_path(path) + else: + logger.warning( + "Url '%s' is ignored: it is neither a file " + "nor a directory.", url, + ) + elif is_url(url): + # Only add url with clear scheme + urls.append(url) + else: + logger.warning( + "Url '%s' is ignored. It is either a non-existing " + "path or lacks a specific scheme.", url, + ) + + return files, urls + + def _candidate_sort_key(self, candidate): + # type: (InstallationCandidate) -> CandidateSortingKey + """ + Function used to generate link sort key for link tuples. + The greater the return value, the more preferred it is. + If not finding wheels, then sorted by version only. + If finding wheels, then the sort order is by version, then: + 1. existing installs + 2. wheels ordered via Wheel.support_index_min(self.valid_tags) + 3. source archives + If prefer_binary was set, then all wheels are sorted above sources. + Note: it was considered to embed this logic into the Link + comparison operators, but then different sdist links + with the same version, would have to be considered equal + """ + support_num = len(self.valid_tags) + build_tag = tuple() # type: BuildTag + binary_preference = 0 + if candidate.location.is_wheel: + # can raise InvalidWheelFilename + wheel = Wheel(candidate.location.filename) + if not wheel.supported(self.valid_tags): + raise UnsupportedWheel( + "%s is not a supported wheel for this platform. It " + "can't be sorted." % wheel.filename + ) + if self.prefer_binary: + binary_preference = 1 + pri = -(wheel.support_index_min(self.valid_tags)) + if wheel.build_tag is not None: + match = re.match(r'^(\d+)(.*)$', wheel.build_tag) + build_tag_groups = match.groups() + build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) + else: # sdist + pri = -(support_num) + return (binary_preference, candidate.version, build_tag, pri) + + def _validate_secure_origin(self, logger, location): + # type: (Logger, Link) -> bool + # Determine if this url used a secure transport mechanism + parsed = urllib_parse.urlparse(str(location)) + origin = (parsed.scheme, parsed.hostname, parsed.port) + + # The protocol to use to see if the protocol matches. + # Don't count the repository type as part of the protocol: in + # cases such as "git+ssh", only use "ssh". (I.e., Only verify against + # the last scheme.) + protocol = origin[0].rsplit('+', 1)[-1] + + # Determine if our origin is a secure origin by looking through our + # hardcoded list of secure origins, as well as any additional ones + # configured on this PackageFinder instance. + for secure_origin in (SECURE_ORIGINS + self.secure_origins): + if protocol != secure_origin[0] and secure_origin[0] != "*": + continue + + try: + # We need to do this decode dance to ensure that we have a + # unicode object, even on Python 2.x. + addr = ipaddress.ip_address( + origin[1] + if ( + isinstance(origin[1], six.text_type) or + origin[1] is None + ) + else origin[1].decode("utf8") + ) + network = ipaddress.ip_network( + secure_origin[1] + if isinstance(secure_origin[1], six.text_type) + # setting secure_origin[1] to proper Union[bytes, str] + # creates problems in other places + else secure_origin[1].decode("utf8") # type: ignore + ) + except ValueError: + # We don't have both a valid address or a valid network, so + # we'll check this origin against hostnames. + if (origin[1] and + origin[1].lower() != secure_origin[1].lower() and + secure_origin[1] != "*"): + continue + else: + # We have a valid address and network, so see if the address + # is contained within the network. + if addr not in network: + continue + + # Check to see if the port patches + if (origin[2] != secure_origin[2] and + secure_origin[2] != "*" and + secure_origin[2] is not None): + continue + + # If we've gotten here, then this origin matches the current + # secure origin and we should return True + return True + + # If we've gotten to this point, then the origin isn't secure and we + # will not accept it as a valid location to search. We will however + # log a warning that we are ignoring it. + logger.warning( + "The repository located at %s is not a trusted or secure host and " + "is being ignored. If this repository is available via HTTPS we " + "recommend you use HTTPS instead, otherwise you may silence " + "this warning and allow it anyway with '--trusted-host %s'.", + parsed.hostname, + parsed.hostname, + ) + + return False + + def _get_index_urls_locations(self, project_name): + # type: (str) -> List[str] + """Returns the locations found via self.index_urls + + Checks the url_name on the main (first in the list) index and + use this url_name to produce all locations + """ + + def mkurl_pypi_url(url): + loc = posixpath.join( + url, + urllib_parse.quote(canonicalize_name(project_name))) + # For maximum compatibility with easy_install, ensure the path + # ends in a trailing slash. Although this isn't in the spec + # (and PyPI can handle it without the slash) some other index + # implementations might break if they relied on easy_install's + # behavior. + if not loc.endswith('/'): + loc = loc + '/' + return loc + + return [mkurl_pypi_url(url) for url in self.index_urls] + + def find_all_candidates(self, project_name): + # type: (str) -> List[Optional[InstallationCandidate]] + """Find all available InstallationCandidate for project_name + + This checks index_urls and find_links. + All versions found are returned as an InstallationCandidate list. + + See _link_package_versions for details on which files are accepted + """ + index_locations = self._get_index_urls_locations(project_name) + index_file_loc, index_url_loc = self._sort_locations(index_locations) + fl_file_loc, fl_url_loc = self._sort_locations( + self.find_links, expand_dir=True, + ) + + file_locations = (Link(url) for url in itertools.chain( + index_file_loc, fl_file_loc, + )) + + # We trust every url that the user has given us whether it was given + # via --index-url or --find-links. + # We want to filter out any thing which does not have a secure origin. + url_locations = [ + link for link in itertools.chain( + (Link(url) for url in index_url_loc), + (Link(url) for url in fl_url_loc), + ) + if self._validate_secure_origin(logger, link) + ] + + logger.debug('%d location(s) to search for versions of %s:', + len(url_locations), project_name) + + for location in url_locations: + logger.debug('* %s', location) + + canonical_name = canonicalize_name(project_name) + formats = self.format_control.get_allowed_formats(canonical_name) + search = Search(project_name, canonical_name, formats) + find_links_versions = self._package_versions( + # We trust every directly linked archive in find_links + (Link(url, '-f') for url in self.find_links), + search + ) + + page_versions = [] + for page in self._get_pages(url_locations, project_name): + logger.debug('Analyzing links from page %s', page.url) + with indent_log(): + page_versions.extend( + self._package_versions(page.iter_links(), search) + ) + + file_versions = self._package_versions(file_locations, search) + if file_versions: + file_versions.sort(reverse=True) + logger.debug( + 'Local files found: %s', + ', '.join([ + url_to_path(candidate.location.url) + for candidate in file_versions + ]) + ) + + # This is an intentional priority ordering + return file_versions + find_links_versions + page_versions + + def find_requirement(self, req, upgrade): + # type: (InstallRequirement, bool) -> Optional[Link] + """Try to find a Link matching req + + Expects req, an InstallRequirement and upgrade, a boolean + Returns a Link if found, + Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise + """ + all_candidates = self.find_all_candidates(req.name) + + # Filter out anything which doesn't match our specifier + compatible_versions = set( + req.specifier.filter( + # We turn the version object into a str here because otherwise + # when we're debundled but setuptools isn't, Python will see + # packaging.version.Version and + # pkg_resources._vendor.packaging.version.Version as different + # types. This way we'll use a str as a common data interchange + # format. If we stop using the pkg_resources provided specifier + # and start using our own, we can drop the cast to str(). + [str(c.version) for c in all_candidates], + prereleases=( + self.allow_all_prereleases + if self.allow_all_prereleases else None + ), + ) + ) + applicable_candidates = [ + # Again, converting to str to deal with debundling. + c for c in all_candidates if str(c.version) in compatible_versions + ] + + if applicable_candidates: + best_candidate = max(applicable_candidates, + key=self._candidate_sort_key) + else: + best_candidate = None + + if req.satisfied_by is not None: + installed_version = parse_version(req.satisfied_by.version) + else: + installed_version = None + + if installed_version is None and best_candidate is None: + logger.critical( + 'Could not find a version that satisfies the requirement %s ' + '(from versions: %s)', + req, + ', '.join( + sorted( + {str(c.version) for c in all_candidates}, + key=parse_version, + ) + ) + ) + + raise DistributionNotFound( + 'No matching distribution found for %s' % req + ) + + best_installed = False + if installed_version and ( + best_candidate is None or + best_candidate.version <= installed_version): + best_installed = True + + if not upgrade and installed_version is not None: + if best_installed: + logger.debug( + 'Existing installed version (%s) is most up-to-date and ' + 'satisfies requirement', + installed_version, + ) + else: + logger.debug( + 'Existing installed version (%s) satisfies requirement ' + '(most up-to-date version is %s)', + installed_version, + best_candidate.version, + ) + return None + + if best_installed: + # We have an existing version, and its the best version + logger.debug( + 'Installed version (%s) is most up-to-date (past versions: ' + '%s)', + installed_version, + ', '.join(sorted(compatible_versions, key=parse_version)) or + "none", + ) + raise BestVersionAlreadyInstalled + + logger.debug( + 'Using version %s (newest of versions: %s)', + best_candidate.version, + ', '.join(sorted(compatible_versions, key=parse_version)) + ) + return best_candidate.location + + def _get_pages(self, locations, project_name): + # type: (Iterable[Link], str) -> Iterable[HTMLPage] + """ + Yields (page, page_url) from the given locations, skipping + locations that have errors. + """ + seen = set() # type: Set[Link] + for location in locations: + if location in seen: + continue + seen.add(location) + + page = _get_html_page(location, session=self.session) + if page is None: + continue + + yield page + + _py_version_re = re.compile(r'-py([123]\.?[0-9]?)$') + + def _sort_links(self, links): + # type: (Iterable[Link]) -> List[Link] + """ + Returns elements of links in order, non-egg links first, egg links + second, while eliminating duplicates + """ + eggs, no_eggs = [], [] + seen = set() # type: Set[Link] + for link in links: + if link not in seen: + seen.add(link) + if link.egg_fragment: + eggs.append(link) + else: + no_eggs.append(link) + return no_eggs + eggs + + def _package_versions( + self, + links, # type: Iterable[Link] + search # type: Search + ): + # type: (...) -> List[Optional[InstallationCandidate]] + result = [] + for link in self._sort_links(links): + v = self._link_package_versions(link, search) + if v is not None: + result.append(v) + return result + + def _log_skipped_link(self, link, reason): + # type: (Link, str) -> None + if link not in self.logged_links: + logger.debug('Skipping link %s; %s', link, reason) + self.logged_links.add(link) + + def _link_package_versions(self, link, search): + # type: (Link, Search) -> Optional[InstallationCandidate] + """Return an InstallationCandidate or None""" + version = None + if link.egg_fragment: + egg_info = link.egg_fragment + ext = link.ext + else: + egg_info, ext = link.splitext() + if not ext: + self._log_skipped_link(link, 'not a file') + return None + if ext not in SUPPORTED_EXTENSIONS: + self._log_skipped_link( + link, 'unsupported archive format: %s' % ext, + ) + return None + if "binary" not in search.formats and ext == WHEEL_EXTENSION: + self._log_skipped_link( + link, 'No binaries permitted for %s' % search.supplied, + ) + return None + if "macosx10" in link.path and ext == '.zip': + self._log_skipped_link(link, 'macosx10 one') + return None + if ext == WHEEL_EXTENSION: + try: + wheel = Wheel(link.filename) + except InvalidWheelFilename: + self._log_skipped_link(link, 'invalid wheel filename') + return None + if canonicalize_name(wheel.name) != search.canonical: + self._log_skipped_link( + link, 'wrong project name (not %s)' % search.supplied) + return None + + if not wheel.supported(self.valid_tags): + self._log_skipped_link( + link, 'it is not compatible with this Python') + return None + + version = wheel.version + + # This should be up by the search.ok_binary check, but see issue 2700. + if "source" not in search.formats and ext != WHEEL_EXTENSION: + self._log_skipped_link( + link, 'No sources permitted for %s' % search.supplied, + ) + return None + + if not version: + version = _egg_info_matches(egg_info, search.canonical) + if not version: + self._log_skipped_link( + link, 'Missing project version for %s' % search.supplied) + return None + + match = self._py_version_re.search(version) + if match: + version = version[:match.start()] + py_version = match.group(1) + if py_version != sys.version[:3]: + self._log_skipped_link( + link, 'Python version is incorrect') + return None + try: + support_this_python = check_requires_python(link.requires_python) + except specifiers.InvalidSpecifier: + logger.debug("Package %s has an invalid Requires-Python entry: %s", + link.filename, link.requires_python) + support_this_python = True + + if not support_this_python: + logger.debug("The package %s is incompatible with the python " + "version in use. Acceptable python versions are: %s", + link, link.requires_python) + return None + logger.debug('Found link %s, version: %s', link, version) + + return InstallationCandidate(search.supplied, version, link) + + +def _find_name_version_sep(egg_info, canonical_name): + # type: (str, str) -> int + """Find the separator's index based on the package's canonical name. + + `egg_info` must be an egg info string for the given package, and + `canonical_name` must be the package's canonical name. + + This function is needed since the canonicalized name does not necessarily + have the same length as the egg info's name part. An example:: + + >>> egg_info = 'foo__bar-1.0' + >>> canonical_name = 'foo-bar' + >>> _find_name_version_sep(egg_info, canonical_name) + 8 + """ + # Project name and version must be separated by one single dash. Find all + # occurrences of dashes; if the string in front of it matches the canonical + # name, this is the one separating the name and version parts. + for i, c in enumerate(egg_info): + if c != "-": + continue + if canonicalize_name(egg_info[:i]) == canonical_name: + return i + raise ValueError("{} does not match {}".format(egg_info, canonical_name)) + + +def _egg_info_matches(egg_info, canonical_name): + # type: (str, str) -> Optional[str] + """Pull the version part out of a string. + + :param egg_info: The string to parse. E.g. foo-2.1 + :param canonical_name: The canonicalized name of the package this + belongs to. + """ + try: + version_start = _find_name_version_sep(egg_info, canonical_name) + 1 + except ValueError: + return None + version = egg_info[version_start:] + if not version: + return None + return version + + +def _determine_base_url(document, page_url): + """Determine the HTML document's base URL. + + This looks for a ```` 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 _get_encoding_from_headers(headers): + """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'] + return None + + +_CLEAN_LINK_RE = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I) + + +def _clean_link(url): + # type: (str) -> str + """Makes sure a link is fully encoded. That is, if a ' ' shows up in + the link, it will be rewritten to %20 (while not over-quoting + % or other characters).""" + return _CLEAN_LINK_RE.sub(lambda match: '%%%2x' % ord(match.group(0)), url) + + +class HTMLPage(object): + """Represents one page, along with its URL""" + + def __init__(self, content, url, headers=None): + # type: (bytes, str, MutableMapping[str, str]) -> None + self.content = content + self.url = url + self.headers = headers + + def __str__(self): + return redact_password_from_url(self.url) + + def iter_links(self): + # type: () -> Iterable[Link] + """Yields all links in the page""" + document = html5lib.parse( + self.content, + transport_encoding=_get_encoding_from_headers(self.headers), + namespaceHTMLElements=False, + ) + base_url = _determine_base_url(document, self.url) + for anchor in document.findall(".//a"): + if anchor.get("href"): + href = anchor.get("href") + url = _clean_link(urllib_parse.urljoin(base_url, href)) + pyrequire = anchor.get('data-requires-python') + pyrequire = unescape(pyrequire) if pyrequire else None + yield Link(url, self.url, requires_python=pyrequire) + + +Search = namedtuple('Search', 'supplied canonical formats') +"""Capture key aspects of a search. + +:attribute supplied: The user supplied package. +:attribute canonical: The canonical package name. +:attribute formats: The formats allowed for this package. Should be a set + with 'binary' or 'source' or both in it. +""" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/locations.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/locations.py new file mode 100644 index 0000000..c6e2a3e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/locations.py @@ -0,0 +1,211 @@ +"""Locations where we look for configs, install stuff, etc""" +from __future__ import absolute_import + +import os +import os.path +import platform +import site +import sys +import sysconfig +from distutils import sysconfig as distutils_sysconfig +from distutils.command.install import SCHEME_KEYS # type: ignore + +from pip._internal.utils import appdirs +from pip._internal.utils.compat import WINDOWS, expanduser +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Any, Union, Dict, List, Optional # noqa: F401 + + +# Application Directories +USER_CACHE_DIR = appdirs.user_cache_dir("pip") + + +DELETE_MARKER_MESSAGE = '''\ +This file is placed here by pip to indicate the source was put +here by pip. + +Once this package is successfully installed this source code will be +deleted (unless you remove this file). +''' +PIP_DELETE_MARKER_FILENAME = 'pip-delete-this-directory.txt' + + +def write_delete_marker_file(directory): + # type: (str) -> None + """ + Write the pip delete marker file into this directory. + """ + filepath = os.path.join(directory, PIP_DELETE_MARKER_FILENAME) + with open(filepath, 'w') as marker_fp: + marker_fp.write(DELETE_MARKER_MESSAGE) + + +def running_under_virtualenv(): + # type: () -> bool + """ + Return True if we're running inside a virtualenv, False otherwise. + + """ + if hasattr(sys, 'real_prefix'): + return True + elif sys.prefix != getattr(sys, "base_prefix", sys.prefix): + return True + + return False + + +def virtualenv_no_global(): + # type: () -> bool + """ + Return True if in a venv and no system site packages. + """ + # this mirrors the logic in virtualenv.py for locating the + # no-global-site-packages.txt file + site_mod_dir = os.path.dirname(os.path.abspath(site.__file__)) + no_global_file = os.path.join(site_mod_dir, 'no-global-site-packages.txt') + if running_under_virtualenv() and os.path.isfile(no_global_file): + return True + else: + return False + + +if running_under_virtualenv(): + src_prefix = os.path.join(sys.prefix, 'src') +else: + # FIXME: keep src in cwd for now (it is not a temporary folder) + try: + src_prefix = os.path.join(os.getcwd(), 'src') + except OSError: + # In case the current working directory has been renamed or deleted + sys.exit( + "The folder you are executing pip from can no longer be found." + ) + +# under macOS + virtualenv sys.prefix is not properly resolved +# it is something like /path/to/python/bin/.. +# Note: using realpath due to tmp dirs on OSX being symlinks +src_prefix = os.path.abspath(src_prefix) + +# FIXME doesn't account for venv linked to global site-packages + +site_packages = sysconfig.get_path("purelib") # type: Optional[str] + +# This is because of a bug in PyPy's sysconfig module, see +# https://bitbucket.org/pypy/pypy/issues/2506/sysconfig-returns-incorrect-paths +# for more information. +if platform.python_implementation().lower() == "pypy": + site_packages = distutils_sysconfig.get_python_lib() +try: + # Use getusersitepackages if this is present, as it ensures that the + # value is initialised properly. + user_site = site.getusersitepackages() +except AttributeError: + user_site = site.USER_SITE +user_dir = expanduser('~') +if WINDOWS: + bin_py = os.path.join(sys.prefix, 'Scripts') + bin_user = os.path.join(user_site, 'Scripts') + # buildout uses 'bin' on Windows too? + if not os.path.exists(bin_py): + bin_py = os.path.join(sys.prefix, 'bin') + bin_user = os.path.join(user_site, 'bin') + + config_basename = 'pip.ini' + + legacy_storage_dir = os.path.join(user_dir, 'pip') + legacy_config_file = os.path.join( + legacy_storage_dir, + config_basename, + ) +else: + bin_py = os.path.join(sys.prefix, 'bin') + bin_user = os.path.join(user_site, 'bin') + + config_basename = 'pip.conf' + + legacy_storage_dir = os.path.join(user_dir, '.pip') + legacy_config_file = os.path.join( + legacy_storage_dir, + config_basename, + ) + # Forcing to use /usr/local/bin for standard macOS framework installs + # Also log to ~/Library/Logs/ for use with the Console.app log viewer + if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/': + bin_py = '/usr/local/bin' + +site_config_files = [ + os.path.join(path, config_basename) + for path in appdirs.site_config_dirs('pip') +] + +venv_config_file = os.path.join(sys.prefix, config_basename) +new_config_file = os.path.join(appdirs.user_config_dir("pip"), config_basename) + + +def distutils_scheme(dist_name, user=False, home=None, root=None, + isolated=False, prefix=None): + # type:(str, bool, str, str, bool, str) -> dict + """ + Return a distutils install scheme + """ + from distutils.dist import Distribution + + scheme = {} + + if isolated: + extra_dist_args = {"script_args": ["--no-user-cfg"]} + else: + extra_dist_args = {} + dist_args = {'name': dist_name} # type: Dict[str, Union[str, List[str]]] + dist_args.update(extra_dist_args) + + d = Distribution(dist_args) + # Ignoring, typeshed issue reported python/typeshed/issues/2567 + d.parse_config_files() + # NOTE: Ignoring type since mypy can't find attributes on 'Command' + i = d.get_command_obj('install', create=True) # type: Any + assert i is not None + # NOTE: setting user or home has the side-effect of creating the home dir + # or user base for installations during finalize_options() + # ideally, we'd prefer a scheme class that has no side-effects. + assert not (user and prefix), "user={} prefix={}".format(user, prefix) + i.user = user or i.user + if user: + i.prefix = "" + i.prefix = prefix or i.prefix + i.home = home or i.home + i.root = root or i.root + i.finalize_options() + for key in SCHEME_KEYS: + scheme[key] = getattr(i, 'install_' + key) + + # install_lib specified in setup.cfg should install *everything* + # into there (i.e. it takes precedence over both purelib and + # platlib). Note, i.install_lib is *always* set after + # finalize_options(); we only want to override here if the user + # has explicitly requested it hence going back to the config + + # Ignoring, typeshed issue reported python/typeshed/issues/2567 + if 'install_lib' in d.get_option_dict('install'): # type: ignore + scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib)) + + if running_under_virtualenv(): + scheme['headers'] = os.path.join( + sys.prefix, + 'include', + 'site', + 'python' + sys.version[:3], + dist_name, + ) + + if root is not None: + path_no_drive = os.path.splitdrive( + os.path.abspath(scheme["headers"]))[1] + scheme["headers"] = os.path.join( + root, + path_no_drive[1:], + ) + + return scheme diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/__init__.py new file mode 100644 index 0000000..7855226 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/__init__.py @@ -0,0 +1,2 @@ +"""A package that contains models that represent entities. +""" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/candidate.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/candidate.py new file mode 100644 index 0000000..4475458 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/candidate.py @@ -0,0 +1,31 @@ +from pip._vendor.packaging.version import parse as parse_version + +from pip._internal.utils.models import KeyBasedCompareMixin +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from pip._vendor.packaging.version import _BaseVersion # noqa: F401 + from pip._internal.models.link import Link # noqa: F401 + from typing import Any, Union # noqa: F401 + + +class InstallationCandidate(KeyBasedCompareMixin): + """Represents a potential "candidate" for installation. + """ + + def __init__(self, project, version, location): + # type: (Any, str, Link) -> None + self.project = project + self.version = parse_version(version) # type: _BaseVersion + self.location = location + + super(InstallationCandidate, self).__init__( + key=(self.project, self.version, self.location), + defining_class=InstallationCandidate + ) + + def __repr__(self): + # type: () -> str + return "".format( + self.project, self.version, self.location, + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/format_control.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/format_control.py new file mode 100644 index 0000000..971a391 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/format_control.py @@ -0,0 +1,73 @@ +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional, Set, FrozenSet # noqa: F401 + + +class FormatControl(object): + """Helper for managing formats from which a package can be installed. + """ + + def __init__(self, no_binary=None, only_binary=None): + # type: (Optional[Set], Optional[Set]) -> None + if no_binary is None: + no_binary = set() + if only_binary is None: + only_binary = set() + + self.no_binary = no_binary + self.only_binary = only_binary + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "{}({}, {})".format( + self.__class__.__name__, + self.no_binary, + self.only_binary + ) + + @staticmethod + def handle_mutual_excludes(value, target, other): + # type: (str, Optional[Set], Optional[Set]) -> None + new = value.split(',') + while ':all:' in new: + other.clear() + target.clear() + target.add(':all:') + del new[:new.index(':all:') + 1] + # Without a none, we want to discard everything as :all: covers it + if ':none:' not in new: + return + for name in new: + if name == ':none:': + target.clear() + continue + name = canonicalize_name(name) + other.discard(name) + target.add(name) + + def get_allowed_formats(self, canonical_name): + # type: (str) -> FrozenSet + result = {"binary", "source"} + if canonical_name in self.only_binary: + result.discard('source') + elif canonical_name in self.no_binary: + result.discard('binary') + elif ':all:' in self.only_binary: + result.discard('source') + elif ':all:' in self.no_binary: + result.discard('binary') + return frozenset(result) + + def disallow_binaries(self): + # type: () -> None + self.handle_mutual_excludes( + ':all:', self.no_binary, self.only_binary, + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/index.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/index.py new file mode 100644 index 0000000..ead1efb --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/index.py @@ -0,0 +1,31 @@ +from pip._vendor.six.moves.urllib import parse as urllib_parse + + +class PackageIndex(object): + """Represents a Package Index and provides easier access to endpoints + """ + + def __init__(self, url, file_storage_domain): + # type: (str, str) -> None + super(PackageIndex, self).__init__() + self.url = url + self.netloc = urllib_parse.urlsplit(url).netloc + self.simple_url = self._url_for_path('simple') + self.pypi_url = self._url_for_path('pypi') + + # This is part of a temporary hack used to block installs of PyPI + # packages which depend on external urls only necessary until PyPI can + # block such packages themselves + self.file_storage_domain = file_storage_domain + + def _url_for_path(self, path): + # type: (str) -> str + return urllib_parse.urljoin(self.url, path) + + +PyPI = PackageIndex( + 'https://pypi.org/', file_storage_domain='files.pythonhosted.org' +) +TestPyPI = PackageIndex( + 'https://test.pypi.org/', file_storage_domain='test-files.pythonhosted.org' +) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/link.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/link.py new file mode 100644 index 0000000..ad2f93e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/models/link.py @@ -0,0 +1,163 @@ +import posixpath +import re + +from pip._vendor.six.moves.urllib import parse as urllib_parse + +from pip._internal.download import path_to_url +from pip._internal.utils.misc import ( + WHEEL_EXTENSION, redact_password_from_url, splitext, +) +from pip._internal.utils.models import KeyBasedCompareMixin +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional, Tuple, Union, Text # noqa: F401 + from pip._internal.index import HTMLPage # noqa: F401 + + +class Link(KeyBasedCompareMixin): + """Represents a parsed link from a Package Index's simple URL + """ + + def __init__(self, url, comes_from=None, requires_python=None): + # type: (str, Optional[Union[str, HTMLPage]], Optional[str]) -> None + """ + url: + url of the resource pointed to (href of the link) + comes_from: + instance of HTMLPage where the link was found, or string. + requires_python: + String containing the `Requires-Python` metadata field, specified + in PEP 345. This may be specified by a data-requires-python + attribute in the HTML link tag, as described in PEP 503. + """ + + # url can be a UNC windows share + if url.startswith('\\\\'): + url = path_to_url(url) + + self.url = url + self.comes_from = comes_from + self.requires_python = requires_python if requires_python else None + + super(Link, self).__init__( + key=(self.url), + defining_class=Link + ) + + def __str__(self): + if self.requires_python: + rp = ' (requires-python:%s)' % self.requires_python + else: + rp = '' + if self.comes_from: + return '%s (from %s)%s' % (redact_password_from_url(self.url), + self.comes_from, rp) + else: + return redact_password_from_url(str(self.url)) + + def __repr__(self): + return '' % self + + @property + def filename(self): + # type: () -> str + _, netloc, path, _, _ = urllib_parse.urlsplit(self.url) + name = posixpath.basename(path.rstrip('/')) or netloc + name = urllib_parse.unquote(name) + assert name, ('URL %r produced no filename' % self.url) + return name + + @property + def scheme(self): + # type: () -> str + return urllib_parse.urlsplit(self.url)[0] + + @property + def netloc(self): + # type: () -> str + return urllib_parse.urlsplit(self.url)[1] + + @property + def path(self): + # type: () -> str + return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2]) + + def splitext(self): + # type: () -> Tuple[str, str] + return splitext(posixpath.basename(self.path.rstrip('/'))) + + @property + def ext(self): + # type: () -> str + return self.splitext()[1] + + @property + def url_without_fragment(self): + # type: () -> str + scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url) + return urllib_parse.urlunsplit((scheme, netloc, path, query, None)) + + _egg_fragment_re = re.compile(r'[#&]egg=([^&]*)') + + @property + def egg_fragment(self): + # type: () -> Optional[str] + match = self._egg_fragment_re.search(self.url) + if not match: + return None + return match.group(1) + + _subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)') + + @property + def subdirectory_fragment(self): + # type: () -> Optional[str] + match = self._subdirectory_fragment_re.search(self.url) + if not match: + return None + return match.group(1) + + _hash_re = re.compile( + r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)' + ) + + @property + def hash(self): + # type: () -> Optional[str] + match = self._hash_re.search(self.url) + if match: + return match.group(2) + return None + + @property + def hash_name(self): + # type: () -> Optional[str] + match = self._hash_re.search(self.url) + if match: + return match.group(1) + return None + + @property + def show_url(self): + # type: () -> Optional[str] + return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0]) + + @property + def is_wheel(self): + # type: () -> bool + return self.ext == WHEEL_EXTENSION + + @property + def is_artifact(self): + # type: () -> bool + """ + Determines if this points to an actual artifact (e.g. a tarball) or if + it points to an "abstract" thing like a path or a VCS location. + """ + from pip._internal.vcs import vcs + + if self.scheme in vcs.all_schemes: + return False + + return True diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/check.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/check.py new file mode 100644 index 0000000..0b56eda --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/check.py @@ -0,0 +1,155 @@ +"""Validation of dependencies of packages +""" + +import logging +from collections import namedtuple + +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.pkg_resources import RequirementParseError + +from pip._internal.operations.prepare import make_abstract_dist +from pip._internal.utils.misc import get_installed_distributions +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +logger = logging.getLogger(__name__) + +if MYPY_CHECK_RUNNING: + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + from typing import ( # noqa: F401 + Any, Callable, Dict, Optional, Set, Tuple, List + ) + + # Shorthands + PackageSet = Dict[str, 'PackageDetails'] + Missing = Tuple[str, Any] + Conflicting = Tuple[str, str, Any] + + MissingDict = Dict[str, List[Missing]] + ConflictingDict = Dict[str, List[Conflicting]] + CheckResult = Tuple[MissingDict, ConflictingDict] + +PackageDetails = namedtuple('PackageDetails', ['version', 'requires']) + + +def create_package_set_from_installed(**kwargs): + # type: (**Any) -> Tuple[PackageSet, bool] + """Converts a list of distributions into a PackageSet. + """ + # Default to using all packages installed on the system + if kwargs == {}: + kwargs = {"local_only": False, "skip": ()} + + package_set = {} + problems = False + for dist in get_installed_distributions(**kwargs): + name = canonicalize_name(dist.project_name) + try: + package_set[name] = PackageDetails(dist.version, dist.requires()) + except RequirementParseError as e: + # Don't crash on broken metadata + logging.warning("Error parsing requirements for %s: %s", name, e) + problems = True + return package_set, problems + + +def check_package_set(package_set, should_ignore=None): + # type: (PackageSet, Optional[Callable[[str], bool]]) -> CheckResult + """Check if a package set is consistent + + If should_ignore is passed, it should be a callable that takes a + package name and returns a boolean. + """ + if should_ignore is None: + def should_ignore(name): + return False + + missing = dict() + conflicting = dict() + + for package_name in package_set: + # Info about dependencies of package_name + missing_deps = set() # type: Set[Missing] + conflicting_deps = set() # type: Set[Conflicting] + + if should_ignore(package_name): + continue + + for req in package_set[package_name].requires: + name = canonicalize_name(req.project_name) # type: str + + # Check if it's missing + if name not in package_set: + missed = True + if req.marker is not None: + missed = req.marker.evaluate() + if missed: + missing_deps.add((name, req)) + continue + + # Check if there's a conflict + version = package_set[name].version # type: str + if not req.specifier.contains(version, prereleases=True): + conflicting_deps.add((name, version, req)) + + if missing_deps: + missing[package_name] = sorted(missing_deps, key=str) + if conflicting_deps: + conflicting[package_name] = sorted(conflicting_deps, key=str) + + return missing, conflicting + + +def check_install_conflicts(to_install): + # type: (List[InstallRequirement]) -> Tuple[PackageSet, CheckResult] + """For checking if the dependency graph would be consistent after \ + installing given requirements + """ + # Start from the current state + package_set, _ = create_package_set_from_installed() + # Install packages + would_be_installed = _simulate_installation_of(to_install, package_set) + + # Only warn about directly-dependent packages; create a whitelist of them + whitelist = _create_whitelist(would_be_installed, package_set) + + return ( + package_set, + check_package_set( + package_set, should_ignore=lambda name: name not in whitelist + ) + ) + + +def _simulate_installation_of(to_install, package_set): + # type: (List[InstallRequirement], PackageSet) -> Set[str] + """Computes the version of packages after installing to_install. + """ + + # Keep track of packages that were installed + installed = set() + + # Modify it as installing requirement_set would (assuming no errors) + for inst_req in to_install: + dist = make_abstract_dist(inst_req).dist() + name = canonicalize_name(dist.key) + package_set[name] = PackageDetails(dist.version, dist.requires()) + + installed.add(name) + + return installed + + +def _create_whitelist(would_be_installed, package_set): + # type: (Set[str], PackageSet) -> Set[str] + packages_affected = set(would_be_installed) + + for package_name in package_set: + if package_name in packages_affected: + continue + + for req in package_set[package_name].requires: + if canonicalize_name(req.name) in packages_affected: + packages_affected.add(package_name) + break + + return packages_affected diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/freeze.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/freeze.py new file mode 100644 index 0000000..388bb73 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/freeze.py @@ -0,0 +1,247 @@ +from __future__ import absolute_import + +import collections +import logging +import os +import re + +from pip._vendor import six +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.pkg_resources import RequirementParseError + +from pip._internal.exceptions import BadCommand, InstallationError +from pip._internal.req.constructors import ( + install_req_from_editable, install_req_from_line, +) +from pip._internal.req.req_file import COMMENT_RE +from pip._internal.utils.misc import ( + dist_is_editable, get_installed_distributions, +) +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Iterator, Optional, List, Container, Set, Dict, Tuple, Iterable, Union + ) + from pip._internal.cache import WheelCache # noqa: F401 + from pip._vendor.pkg_resources import ( # noqa: F401 + Distribution, Requirement + ) + + RequirementInfo = Tuple[Optional[Union[str, Requirement]], bool, List[str]] + + +logger = logging.getLogger(__name__) + + +def freeze( + requirement=None, # type: Optional[List[str]] + find_links=None, # type: Optional[List[str]] + local_only=None, # type: Optional[bool] + user_only=None, # type: Optional[bool] + skip_regex=None, # type: Optional[str] + isolated=False, # type: bool + wheel_cache=None, # type: Optional[WheelCache] + exclude_editable=False, # type: bool + skip=() # type: Container[str] +): + # type: (...) -> Iterator[str] + find_links = find_links or [] + skip_match = None + + if skip_regex: + skip_match = re.compile(skip_regex).search + + for link in find_links: + yield '-f %s' % link + installations = {} # type: Dict[str, FrozenRequirement] + for dist in get_installed_distributions(local_only=local_only, + skip=(), + user_only=user_only): + try: + req = FrozenRequirement.from_dist(dist) + except RequirementParseError: + logger.warning( + "Could not parse requirement: %s", + dist.project_name + ) + continue + if exclude_editable and req.editable: + continue + installations[req.name] = req + + if requirement: + # the options that don't get turned into an InstallRequirement + # should only be emitted once, even if the same option is in multiple + # requirements files, so we need to keep track of what has been emitted + # so that we don't emit it again if it's seen again + emitted_options = set() # type: Set[str] + # keep track of which files a requirement is in so that we can + # give an accurate warning if a requirement appears multiple times. + req_files = collections.defaultdict(list) # type: Dict[str, List[str]] + for req_file_path in requirement: + with open(req_file_path) as req_file: + for line in req_file: + if (not line.strip() or + line.strip().startswith('#') or + (skip_match and skip_match(line)) or + line.startswith(( + '-r', '--requirement', + '-Z', '--always-unzip', + '-f', '--find-links', + '-i', '--index-url', + '--pre', + '--trusted-host', + '--process-dependency-links', + '--extra-index-url'))): + line = line.rstrip() + if line not in emitted_options: + emitted_options.add(line) + yield line + continue + + if line.startswith('-e') or line.startswith('--editable'): + if line.startswith('-e'): + line = line[2:].strip() + else: + line = line[len('--editable'):].strip().lstrip('=') + line_req = install_req_from_editable( + line, + isolated=isolated, + wheel_cache=wheel_cache, + ) + else: + line_req = install_req_from_line( + COMMENT_RE.sub('', line).strip(), + isolated=isolated, + wheel_cache=wheel_cache, + ) + + if not line_req.name: + logger.info( + "Skipping line in requirement file [%s] because " + "it's not clear what it would install: %s", + req_file_path, line.strip(), + ) + logger.info( + " (add #egg=PackageName to the URL to avoid" + " this warning)" + ) + elif line_req.name not in installations: + # either it's not installed, or it is installed + # but has been processed already + if not req_files[line_req.name]: + logger.warning( + "Requirement file [%s] contains %s, but " + "package %r is not installed", + req_file_path, + COMMENT_RE.sub('', line).strip(), line_req.name + ) + else: + req_files[line_req.name].append(req_file_path) + else: + yield str(installations[line_req.name]).rstrip() + del installations[line_req.name] + req_files[line_req.name].append(req_file_path) + + # Warn about requirements that were included multiple times (in a + # single requirements file or in different requirements files). + for name, files in six.iteritems(req_files): + if len(files) > 1: + logger.warning("Requirement %s included multiple times [%s]", + name, ', '.join(sorted(set(files)))) + + yield( + '## The following requirements were added by ' + 'pip freeze:' + ) + for installation in sorted( + installations.values(), key=lambda x: x.name.lower()): + if canonicalize_name(installation.name) not in skip: + yield str(installation).rstrip() + + +def get_requirement_info(dist): + # type: (Distribution) -> RequirementInfo + """ + Compute and return values (req, editable, comments) for use in + FrozenRequirement.from_dist(). + """ + if not dist_is_editable(dist): + return (None, False, []) + + location = os.path.normcase(os.path.abspath(dist.location)) + + from pip._internal.vcs import vcs, RemoteNotFoundError + vc_type = vcs.get_backend_type(location) + + if not vc_type: + req = dist.as_requirement() + logger.debug( + 'No VCS found for editable requirement {!r} in: {!r}', req, + location, + ) + comments = [ + '# Editable install with no version control ({})'.format(req) + ] + return (location, True, comments) + + try: + req = vc_type.get_src_requirement(location, dist.project_name) + except RemoteNotFoundError: + req = dist.as_requirement() + comments = [ + '# Editable {} install with no remote ({})'.format( + vc_type.__name__, req, + ) + ] + return (location, True, comments) + + except BadCommand: + logger.warning( + 'cannot determine version of editable source in %s ' + '(%s command not found in path)', + location, + vc_type.name, + ) + return (None, True, []) + + except InstallationError as exc: + logger.warning( + "Error when trying to get requirement for VCS system %s, " + "falling back to uneditable format", exc + ) + else: + if req is not None: + return (req, True, []) + + logger.warning( + 'Could not determine repository location of %s', location + ) + comments = ['## !! Could not determine repository location'] + + return (None, False, comments) + + +class FrozenRequirement(object): + def __init__(self, name, req, editable, comments=()): + # type: (str, Union[str, Requirement], bool, Iterable[str]) -> None + self.name = name + self.req = req + self.editable = editable + self.comments = comments + + @classmethod + def from_dist(cls, dist): + # type: (Distribution) -> FrozenRequirement + req, editable, comments = get_requirement_info(dist) + if req is None: + req = dist.as_requirement() + + return cls(dist.project_name, req, editable, comments=comments) + + def __str__(self): + req = self.req + if self.editable: + req = '-e %s' % req + return '\n'.join(list(self.comments) + [str(req)]) + '\n' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/prepare.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/prepare.py new file mode 100644 index 0000000..4f31dd5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/operations/prepare.py @@ -0,0 +1,413 @@ +"""Prepares a distribution for installation +""" + +import logging +import os + +from pip._vendor import pkg_resources, requests + +from pip._internal.build_env import BuildEnvironment +from pip._internal.download import ( + is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path, +) +from pip._internal.exceptions import ( + DirectoryUrlHashUnsupported, HashUnpinned, InstallationError, + PreviousBuildDirError, VcsHashUnsupported, +) +from pip._internal.utils.compat import expanduser +from pip._internal.utils.hashes import MissingHashes +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import display_path, normalize_path +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.vcs import vcs + +if MYPY_CHECK_RUNNING: + from typing import Any, Optional # noqa: F401 + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + from pip._internal.index import PackageFinder # noqa: F401 + from pip._internal.download import PipSession # noqa: F401 + from pip._internal.req.req_tracker import RequirementTracker # noqa: F401 + +logger = logging.getLogger(__name__) + + +def make_abstract_dist(req): + # type: (InstallRequirement) -> DistAbstraction + """Factory to make an abstract dist object. + + Preconditions: Either an editable req with a source_dir, or satisfied_by or + a wheel link, or a non-editable req with a source_dir. + + :return: A concrete DistAbstraction. + """ + if req.editable: + return IsSDist(req) + elif req.link and req.link.is_wheel: + return IsWheel(req) + else: + return IsSDist(req) + + +class DistAbstraction(object): + """Abstracts out the wheel vs non-wheel Resolver.resolve() logic. + + The requirements for anything installable are as follows: + - we must be able to determine the requirement name + (or we can't correctly handle the non-upgrade case). + - we must be able to generate a list of run-time dependencies + without installing any additional packages (or we would + have to either burn time by doing temporary isolated installs + or alternatively violate pips 'don't start installing unless + all requirements are available' rule - neither of which are + desirable). + - for packages with setup requirements, we must also be able + to determine their requirements without installing additional + packages (for the same reason as run-time dependencies) + - we must be able to create a Distribution object exposing the + above metadata. + """ + + def __init__(self, req): + # type: (InstallRequirement) -> None + self.req = req # type: InstallRequirement + + def dist(self): + # type: () -> Any + """Return a setuptools Dist object.""" + raise NotImplementedError + + def prep_for_dist(self, finder, build_isolation): + # type: (PackageFinder, bool) -> Any + """Ensure that we can get a Dist for this requirement.""" + raise NotImplementedError + + +class IsWheel(DistAbstraction): + + def dist(self): + # type: () -> pkg_resources.Distribution + return list(pkg_resources.find_distributions( + self.req.source_dir))[0] + + def prep_for_dist(self, finder, build_isolation): + # type: (PackageFinder, bool) -> Any + # FIXME:https://github.com/pypa/pip/issues/1112 + pass + + +class IsSDist(DistAbstraction): + + def dist(self): + return self.req.get_dist() + + def prep_for_dist(self, finder, build_isolation): + # type: (PackageFinder, bool) -> None + # Prepare for building. We need to: + # 1. Load pyproject.toml (if it exists) + # 2. Set up the build environment + + self.req.load_pyproject_toml() + should_isolate = self.req.use_pep517 and build_isolation + + def _raise_conflicts(conflicting_with, conflicting_reqs): + raise InstallationError( + "Some build dependencies for %s conflict with %s: %s." % ( + self.req, conflicting_with, ', '.join( + '%s is incompatible with %s' % (installed, wanted) + for installed, wanted in sorted(conflicting)))) + + if should_isolate: + # Isolate in a BuildEnvironment and install the build-time + # requirements. + self.req.build_env = BuildEnvironment() + self.req.build_env.install_requirements( + finder, self.req.pyproject_requires, 'overlay', + "Installing build dependencies" + ) + conflicting, missing = self.req.build_env.check_requirements( + self.req.requirements_to_check + ) + if conflicting: + _raise_conflicts("PEP 517/518 supported requirements", + conflicting) + if missing: + logger.warning( + "Missing build requirements in pyproject.toml for %s.", + self.req, + ) + logger.warning( + "The project does not specify a build backend, and " + "pip cannot fall back to setuptools without %s.", + " and ".join(map(repr, sorted(missing))) + ) + # Install any extra build dependencies that the backend requests. + # This must be done in a second pass, as the pyproject.toml + # dependencies must be installed before we can call the backend. + with self.req.build_env: + # We need to have the env active when calling the hook. + self.req.spin_message = "Getting requirements to build wheel" + reqs = self.req.pep517_backend.get_requires_for_build_wheel() + conflicting, missing = self.req.build_env.check_requirements(reqs) + if conflicting: + _raise_conflicts("the backend dependencies", conflicting) + self.req.build_env.install_requirements( + finder, missing, 'normal', + "Installing backend dependencies" + ) + + self.req.prepare_metadata() + self.req.assert_source_matches_version() + + +class Installed(DistAbstraction): + + def dist(self): + # type: () -> pkg_resources.Distribution + return self.req.satisfied_by + + def prep_for_dist(self, finder, build_isolation): + # type: (PackageFinder, bool) -> Any + pass + + +class RequirementPreparer(object): + """Prepares a Requirement + """ + + def __init__( + self, + build_dir, # type: str + download_dir, # type: Optional[str] + src_dir, # type: str + wheel_download_dir, # type: Optional[str] + progress_bar, # type: str + build_isolation, # type: bool + req_tracker # type: RequirementTracker + ): + # type: (...) -> None + super(RequirementPreparer, self).__init__() + + self.src_dir = src_dir + self.build_dir = build_dir + self.req_tracker = req_tracker + + # Where still packed archives should be written to. If None, they are + # not saved, and are deleted immediately after unpacking. + self.download_dir = download_dir + + # Where still-packed .whl files should be written to. If None, they are + # written to the download_dir parameter. Separate to download_dir to + # permit only keeping wheel archives for pip wheel. + if wheel_download_dir: + wheel_download_dir = normalize_path(wheel_download_dir) + self.wheel_download_dir = wheel_download_dir + + # NOTE + # download_dir and wheel_download_dir overlap semantically and may + # be combined if we're willing to have non-wheel archives present in + # the wheelhouse output by 'pip wheel'. + + self.progress_bar = progress_bar + + # Is build isolation allowed? + self.build_isolation = build_isolation + + @property + def _download_should_save(self): + # type: () -> bool + # TODO: Modify to reduce indentation needed + if self.download_dir: + self.download_dir = expanduser(self.download_dir) + if os.path.exists(self.download_dir): + return True + else: + logger.critical('Could not find download directory') + raise InstallationError( + "Could not find or access download directory '%s'" + % display_path(self.download_dir)) + return False + + def prepare_linked_requirement( + self, + req, # type: InstallRequirement + session, # type: PipSession + finder, # type: PackageFinder + upgrade_allowed, # type: bool + require_hashes # type: bool + ): + # type: (...) -> DistAbstraction + """Prepare a requirement that would be obtained from req.link + """ + # TODO: Breakup into smaller functions + if req.link and req.link.scheme == 'file': + path = url_to_path(req.link.url) + logger.info('Processing %s', display_path(path)) + else: + logger.info('Collecting %s', req) + + with indent_log(): + # @@ if filesystem packages are not marked + # editable in a req, a non deterministic error + # occurs when the script attempts to unpack the + # build directory + req.ensure_has_source_dir(self.build_dir) + # If a checkout exists, it's unwise to keep going. version + # inconsistencies are logged later, but do not fail the + # installation. + # FIXME: this won't upgrade when there's an existing + # package unpacked in `req.source_dir` + # package unpacked in `req.source_dir` + if os.path.exists(os.path.join(req.source_dir, 'setup.py')): + raise PreviousBuildDirError( + "pip can't proceed with requirements '%s' due to a" + " pre-existing build directory (%s). This is " + "likely due to a previous installation that failed" + ". pip is being responsible and not assuming it " + "can delete this. Please delete it and try again." + % (req, req.source_dir) + ) + req.populate_link(finder, upgrade_allowed, require_hashes) + + # We can't hit this spot and have populate_link return None. + # req.satisfied_by is None here (because we're + # guarded) and upgrade has no impact except when satisfied_by + # is not None. + # Then inside find_requirement existing_applicable -> False + # If no new versions are found, DistributionNotFound is raised, + # otherwise a result is guaranteed. + assert req.link + link = req.link + + # Now that we have the real link, we can tell what kind of + # requirements we have and raise some more informative errors + # than otherwise. (For example, we can raise VcsHashUnsupported + # for a VCS URL rather than HashMissing.) + if require_hashes: + # We could check these first 2 conditions inside + # unpack_url and save repetition of conditions, but then + # we would report less-useful error messages for + # unhashable requirements, complaining that there's no + # hash provided. + if is_vcs_url(link): + raise VcsHashUnsupported() + elif is_file_url(link) and is_dir_url(link): + raise DirectoryUrlHashUnsupported() + if not req.original_link and not req.is_pinned: + # Unpinned packages are asking for trouble when a new + # version is uploaded. This isn't a security check, but + # it saves users a surprising hash mismatch in the + # future. + # + # file:/// URLs aren't pinnable, so don't complain + # about them not being pinned. + raise HashUnpinned() + + hashes = req.hashes(trust_internet=not require_hashes) + if require_hashes and not hashes: + # Known-good hashes are missing for this requirement, so + # shim it with a facade object that will provoke hash + # computation and then raise a HashMissing exception + # showing the user what the hash should be. + hashes = MissingHashes() + + try: + download_dir = self.download_dir + # We always delete unpacked sdists after pip ran. + autodelete_unpacked = True + if req.link.is_wheel and self.wheel_download_dir: + # when doing 'pip wheel` we download wheels to a + # dedicated dir. + download_dir = self.wheel_download_dir + if req.link.is_wheel: + if download_dir: + # When downloading, we only unpack wheels to get + # metadata. + autodelete_unpacked = True + else: + # When installing a wheel, we use the unpacked + # wheel. + autodelete_unpacked = False + unpack_url( + req.link, req.source_dir, + download_dir, autodelete_unpacked, + session=session, hashes=hashes, + progress_bar=self.progress_bar + ) + except requests.HTTPError as exc: + logger.critical( + 'Could not install requirement %s because of error %s', + req, + exc, + ) + raise InstallationError( + 'Could not install requirement %s because of HTTP ' + 'error %s for URL %s' % + (req, exc, req.link) + ) + abstract_dist = make_abstract_dist(req) + with self.req_tracker.track(req): + abstract_dist.prep_for_dist(finder, self.build_isolation) + if self._download_should_save: + # Make a .zip of the source_dir we already created. + if req.link.scheme in vcs.all_schemes: + req.archive(self.download_dir) + return abstract_dist + + def prepare_editable_requirement( + self, + req, # type: InstallRequirement + require_hashes, # type: bool + use_user_site, # type: bool + finder # type: PackageFinder + ): + # type: (...) -> DistAbstraction + """Prepare an editable requirement + """ + assert req.editable, "cannot prepare a non-editable req as editable" + + logger.info('Obtaining %s', req) + + with indent_log(): + if require_hashes: + raise InstallationError( + 'The editable requirement %s cannot be installed when ' + 'requiring hashes, because there is no single file to ' + 'hash.' % req + ) + req.ensure_has_source_dir(self.src_dir) + req.update_editable(not self._download_should_save) + + abstract_dist = make_abstract_dist(req) + with self.req_tracker.track(req): + abstract_dist.prep_for_dist(finder, self.build_isolation) + + if self._download_should_save: + req.archive(self.download_dir) + req.check_if_exists(use_user_site) + + return abstract_dist + + def prepare_installed_requirement(self, req, require_hashes, skip_reason): + # type: (InstallRequirement, bool, Optional[str]) -> DistAbstraction + """Prepare an already-installed requirement + """ + assert req.satisfied_by, "req should have been satisfied but isn't" + assert skip_reason is not None, ( + "did not get skip reason skipped but req.satisfied_by " + "is set to %r" % (req.satisfied_by,) + ) + logger.info( + 'Requirement %s: %s (%s)', + skip_reason, req, req.satisfied_by.version + ) + with indent_log(): + if require_hashes: + logger.debug( + 'Since it is already installed, we are trusting this ' + 'package without checking its hash. To ensure a ' + 'completely repeatable environment, install into an ' + 'empty virtualenv.' + ) + abstract_dist = Installed(req) + + return abstract_dist diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pep425tags.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pep425tags.py new file mode 100644 index 0000000..1e782d1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pep425tags.py @@ -0,0 +1,381 @@ +"""Generate and work with PEP 425 Compatibility Tags.""" +from __future__ import absolute_import + +import distutils.util +import logging +import platform +import re +import sys +import sysconfig +import warnings +from collections import OrderedDict + +import pip._internal.utils.glibc +from pip._internal.utils.compat import get_extension_suffixes +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Tuple, Callable, List, Optional, Union, Dict + ) + + Pep425Tag = Tuple[str, str, str] + +logger = logging.getLogger(__name__) + +_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') + + +def get_config_var(var): + # type: (str) -> Optional[str] + try: + return sysconfig.get_config_var(var) + except IOError as e: # Issue #1074 + warnings.warn("{}".format(e), RuntimeWarning) + return None + + +def get_abbr_impl(): + # type: () -> str + """Return abbreviated implementation name.""" + if hasattr(sys, 'pypy_version_info'): + pyimpl = 'pp' + elif sys.platform.startswith('java'): + pyimpl = 'jy' + elif sys.platform == 'cli': + pyimpl = 'ip' + else: + pyimpl = 'cp' + return pyimpl + + +def get_impl_ver(): + # type: () -> str + """Return implementation version.""" + impl_ver = get_config_var("py_version_nodot") + if not impl_ver or get_abbr_impl() == 'pp': + impl_ver = ''.join(map(str, get_impl_version_info())) + return impl_ver + + +def get_impl_version_info(): + # type: () -> Tuple[int, ...] + """Return sys.version_info-like tuple for use in decrementing the minor + version.""" + if get_abbr_impl() == 'pp': + # as per https://github.com/pypa/pip/issues/2882 + # attrs exist only on pypy + return (sys.version_info[0], + sys.pypy_version_info.major, # type: ignore + sys.pypy_version_info.minor) # type: ignore + else: + return sys.version_info[0], sys.version_info[1] + + +def get_impl_tag(): + # type: () -> str + """ + Returns the Tag for this specific implementation. + """ + return "{}{}".format(get_abbr_impl(), get_impl_ver()) + + +def get_flag(var, fallback, expected=True, warn=True): + # type: (str, Callable[..., bool], Union[bool, int], bool) -> bool + """Use a fallback method for determining SOABI flags if the needed config + var is unset or unavailable.""" + val = get_config_var(var) + if val is None: + if warn: + logger.debug("Config variable '%s' is unset, Python ABI tag may " + "be incorrect", var) + return fallback() + return val == expected + + +def get_abi_tag(): + # type: () -> Optional[str] + """Return the ABI tag based on SOABI (if available) or emulate SOABI + (CPython 2, PyPy).""" + soabi = get_config_var('SOABI') + impl = get_abbr_impl() + if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): + d = '' + m = '' + u = '' + if get_flag('Py_DEBUG', + lambda: hasattr(sys, 'gettotalrefcount'), + warn=(impl == 'cp')): + d = 'd' + if get_flag('WITH_PYMALLOC', + lambda: impl == 'cp', + warn=(impl == 'cp')): + m = 'm' + if get_flag('Py_UNICODE_SIZE', + lambda: sys.maxunicode == 0x10ffff, + expected=4, + warn=(impl == 'cp' and + sys.version_info < (3, 3))) \ + and sys.version_info < (3, 3): + u = 'u' + abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) + elif soabi and soabi.startswith('cpython-'): + abi = 'cp' + soabi.split('-')[1] + elif soabi: + abi = soabi.replace('.', '_').replace('-', '_') + else: + abi = None + return abi + + +def _is_running_32bit(): + # type: () -> bool + return sys.maxsize == 2147483647 + + +def get_platform(): + # type: () -> str + """Return our platform name 'win32', 'linux_x86_64'""" + if sys.platform == 'darwin': + # distutils.util.get_platform() returns the release based on the value + # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may + # be significantly older than the user's current machine. + release, _, machine = platform.mac_ver() + split_ver = release.split('.') + + if machine == "x86_64" and _is_running_32bit(): + machine = "i386" + elif machine == "ppc64" and _is_running_32bit(): + machine = "ppc" + + return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) + + # XXX remove distutils dependency + result = distutils.util.get_platform().replace('.', '_').replace('-', '_') + if result == "linux_x86_64" and _is_running_32bit(): + # 32 bit Python program (running on a 64 bit Linux): pip should only + # install and run 32 bit compiled extensions in that case. + result = "linux_i686" + + return result + + +def is_manylinux1_compatible(): + # type: () -> bool + # Only Linux, and only x86-64 / i686 + if get_platform() not in {"linux_x86_64", "linux_i686"}: + return False + + # Check for presence of _manylinux module + try: + import _manylinux + return bool(_manylinux.manylinux1_compatible) + except (ImportError, AttributeError): + # Fall through to heuristic check below + pass + + # Check glibc version. CentOS 5 uses glibc 2.5. + return pip._internal.utils.glibc.have_compatible_glibc(2, 5) + + +def is_manylinux2010_compatible(): + # type: () -> bool + # Only Linux, and only x86-64 / i686 + if get_platform() not in {"linux_x86_64", "linux_i686"}: + return False + + # Check for presence of _manylinux module + try: + import _manylinux + return bool(_manylinux.manylinux2010_compatible) + except (ImportError, AttributeError): + # Fall through to heuristic check below + pass + + # Check glibc version. CentOS 6 uses glibc 2.12. + return pip._internal.utils.glibc.have_compatible_glibc(2, 12) + + +def get_darwin_arches(major, minor, machine): + # type: (int, int, str) -> List[str] + """Return a list of supported arches (including group arches) for + the given major, minor and machine architecture of an macOS machine. + """ + arches = [] + + def _supports_arch(major, minor, arch): + # type: (int, int, str) -> bool + # Looking at the application support for macOS versions in the chart + # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears + # our timeline looks roughly like: + # + # 10.0 - Introduces ppc support. + # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64 + # and x86_64 support is CLI only, and cannot be used for GUI + # applications. + # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications. + # 10.6 - Drops support for ppc64 + # 10.7 - Drops support for ppc + # + # Given that we do not know if we're installing a CLI or a GUI + # application, we must be conservative and assume it might be a GUI + # application and behave as if ppc64 and x86_64 support did not occur + # until 10.5. + # + # Note: The above information is taken from the "Application support" + # column in the chart not the "Processor support" since I believe + # that we care about what instruction sets an application can use + # not which processors the OS supports. + if arch == 'ppc': + return (major, minor) <= (10, 5) + if arch == 'ppc64': + return (major, minor) == (10, 5) + if arch == 'i386': + return (major, minor) >= (10, 4) + if arch == 'x86_64': + return (major, minor) >= (10, 5) + if arch in groups: + for garch in groups[arch]: + if _supports_arch(major, minor, garch): + return True + return False + + groups = OrderedDict([ + ("fat", ("i386", "ppc")), + ("intel", ("x86_64", "i386")), + ("fat64", ("x86_64", "ppc64")), + ("fat32", ("x86_64", "i386", "ppc")), + ]) # type: Dict[str, Tuple[str, ...]] + + if _supports_arch(major, minor, machine): + arches.append(machine) + + for garch in groups: + if machine in groups[garch] and _supports_arch(major, minor, garch): + arches.append(garch) + + arches.append('universal') + + return arches + + +def get_all_minor_versions_as_strings(version_info): + # type: (Tuple[int, ...]) -> List[str] + versions = [] + major = version_info[:-1] + # Support all previous minor Python versions. + for minor in range(version_info[-1], -1, -1): + versions.append(''.join(map(str, major + (minor,)))) + return versions + + +def get_supported( + versions=None, # type: Optional[List[str]] + noarch=False, # type: bool + platform=None, # type: Optional[str] + impl=None, # type: Optional[str] + abi=None # type: Optional[str] +): + # type: (...) -> List[Pep425Tag] + """Return a list of supported tags for each version specified in + `versions`. + + :param versions: a list of string versions, of the form ["33", "32"], + or None. The first version will be assumed to support our ABI. + :param platform: specify the exact platform you want valid + tags for, or None. If None, use the local system platform. + :param impl: specify the exact implementation you want valid + tags for, or None. If None, use the local interpreter impl. + :param abi: specify the exact abi you want valid + tags for, or None. If None, use the local interpreter abi. + """ + supported = [] + + # Versions must be given with respect to the preference + if versions is None: + version_info = get_impl_version_info() + versions = get_all_minor_versions_as_strings(version_info) + + impl = impl or get_abbr_impl() + + abis = [] # type: List[str] + + abi = abi or get_abi_tag() + if abi: + abis[0:0] = [abi] + + abi3s = set() + for suffix in get_extension_suffixes(): + if suffix.startswith('.abi'): + abi3s.add(suffix.split('.', 2)[1]) + + abis.extend(sorted(list(abi3s))) + + abis.append('none') + + if not noarch: + arch = platform or get_platform() + arch_prefix, arch_sep, arch_suffix = arch.partition('_') + if arch.startswith('macosx'): + # support macosx-10.6-intel on macosx-10.9-x86_64 + match = _osx_arch_pat.match(arch) + if match: + name, major, minor, actual_arch = match.groups() + tpl = '{}_{}_%i_%s'.format(name, major) + arches = [] + for m in reversed(range(int(minor) + 1)): + for a in get_darwin_arches(int(major), m, actual_arch): + arches.append(tpl % (m, a)) + else: + # arch pattern didn't match (?!) + arches = [arch] + elif arch_prefix == 'manylinux2010': + # manylinux1 wheels run on most manylinux2010 systems with the + # exception of wheels depending on ncurses. PEP 571 states + # manylinux1 wheels should be considered manylinux2010 wheels: + # https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels + arches = [arch, 'manylinux1' + arch_sep + arch_suffix] + elif platform is None: + arches = [] + if is_manylinux2010_compatible(): + arches.append('manylinux2010' + arch_sep + arch_suffix) + if is_manylinux1_compatible(): + arches.append('manylinux1' + arch_sep + arch_suffix) + arches.append(arch) + else: + arches = [arch] + + # Current version, current API (built specifically for our Python): + for abi in abis: + for arch in arches: + supported.append(('%s%s' % (impl, versions[0]), abi, arch)) + + # abi3 modules compatible with older version of Python + for version in versions[1:]: + # abi3 was introduced in Python 3.2 + if version in {'31', '30'}: + break + for abi in abi3s: # empty set if not Python 3 + for arch in arches: + supported.append(("%s%s" % (impl, version), abi, arch)) + + # Has binaries, does not use the Python API: + for arch in arches: + supported.append(('py%s' % (versions[0][0]), 'none', arch)) + + # No abi / arch, but requires our implementation: + supported.append(('%s%s' % (impl, versions[0]), 'none', 'any')) + # Tagged specifically as being cross-version compatible + # (with just the major version specified) + supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) + + # No abi / arch, generic Python + for i, version in enumerate(versions): + supported.append(('py%s' % (version,), 'none', 'any')) + if i == 0: + supported.append(('py%s' % (version[0]), 'none', 'any')) + + return supported + + +implementation_tag = get_impl_tag() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pyproject.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pyproject.py new file mode 100644 index 0000000..8d739a6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/pyproject.py @@ -0,0 +1,171 @@ +from __future__ import absolute_import + +import io +import os +import sys + +from pip._vendor import pytoml, six + +from pip._internal.exceptions import InstallationError +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Any, Tuple, Optional, List # noqa: F401 + + +def _is_list_of_str(obj): + # type: (Any) -> bool + return ( + isinstance(obj, list) and + all(isinstance(item, six.string_types) for item in obj) + ) + + +def make_pyproject_path(setup_py_dir): + # type: (str) -> str + path = os.path.join(setup_py_dir, 'pyproject.toml') + + # Python2 __file__ should not be unicode + if six.PY2 and isinstance(path, six.text_type): + path = path.encode(sys.getfilesystemencoding()) + + return path + + +def load_pyproject_toml( + use_pep517, # type: Optional[bool] + pyproject_toml, # type: str + setup_py, # type: str + req_name # type: str +): + # type: (...) -> Optional[Tuple[List[str], str, List[str]]] + """Load the pyproject.toml file. + + Parameters: + use_pep517 - Has the user requested PEP 517 processing? None + means the user hasn't explicitly specified. + pyproject_toml - Location of the project's pyproject.toml file + setup_py - Location of the project's setup.py file + req_name - The name of the requirement we're processing (for + error reporting) + + Returns: + None if we should use the legacy code path, otherwise a tuple + ( + requirements from pyproject.toml, + name of PEP 517 backend, + requirements we should check are installed after setting + up the build environment + ) + """ + has_pyproject = os.path.isfile(pyproject_toml) + has_setup = os.path.isfile(setup_py) + + if has_pyproject: + with io.open(pyproject_toml, encoding="utf-8") as f: + pp_toml = pytoml.load(f) + build_system = pp_toml.get("build-system") + else: + build_system = None + + # The following cases must use PEP 517 + # We check for use_pep517 being non-None and falsey because that means + # the user explicitly requested --no-use-pep517. The value 0 as + # opposed to False can occur when the value is provided via an + # environment variable or config file option (due to the quirk of + # strtobool() returning an integer in pip's configuration code). + if has_pyproject and not has_setup: + if use_pep517 is not None and not use_pep517: + raise InstallationError( + "Disabling PEP 517 processing is invalid: " + "project does not have a setup.py" + ) + use_pep517 = True + elif build_system and "build-backend" in build_system: + if use_pep517 is not None and not use_pep517: + raise InstallationError( + "Disabling PEP 517 processing is invalid: " + "project specifies a build backend of {} " + "in pyproject.toml".format( + build_system["build-backend"] + ) + ) + use_pep517 = True + + # 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. + elif use_pep517 is None: + use_pep517 = has_pyproject + + # At this point, we know whether we're going to use PEP 517. + assert use_pep517 is not None + + # If we're using the legacy code path, there is nothing further + # for us to do here. + if not use_pep517: + return None + + if build_system is None: + # Either the user has a pyproject.toml with no build-system + # section, or the user has no pyproject.toml, but has opted in + # explicitly via --use-pep517. + # In the absence of any explicit backend specification, we + # assume the setuptools backend that most closely emulates the + # traditional direct setup.py execution, and require wheel and + # a version of setuptools that supports that backend. + + build_system = { + "requires": ["setuptools>=40.8.0", "wheel"], + "build-backend": "setuptools.build_meta:__legacy__", + } + + # If we're using PEP 517, we have build system information (either + # from pyproject.toml, or defaulted by the code above). + # Note that at this point, we do not know if the user has actually + # specified a backend, though. + assert build_system is not None + + # 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" + )) + ) + + # 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.", + )) + + backend = build_system.get("build-backend") + check = [] # type: List[str] + 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 + # 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 + # adopted PEP 518 early for the ability to specify requirements to + # execute setup.py, but never considered needing to mention the build + # 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"] + + return (requires, backend, check) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/__init__.py new file mode 100644 index 0000000..5e4eb92 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/__init__.py @@ -0,0 +1,77 @@ +from __future__ import absolute_import + +import logging + +from .req_install import InstallRequirement +from .req_set import RequirementSet +from .req_file import parse_requirements +from pip._internal.utils.logging import indent_log +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import List, Sequence # noqa: F401 + +__all__ = [ + "RequirementSet", "InstallRequirement", + "parse_requirements", "install_given_reqs", +] + +logger = logging.getLogger(__name__) + + +def install_given_reqs( + to_install, # type: List[InstallRequirement] + install_options, # type: List[str] + global_options=(), # type: Sequence[str] + *args, **kwargs +): + # type: (...) -> List[InstallRequirement] + """ + Install everything in the given list. + + (to be called after having downloaded and unpacked the packages) + """ + + if to_install: + logger.info( + 'Installing collected packages: %s', + ', '.join([req.name for req in to_install]), + ) + + with indent_log(): + for requirement in to_install: + if requirement.conflicts_with: + logger.info( + 'Found existing installation: %s', + requirement.conflicts_with, + ) + with indent_log(): + uninstalled_pathset = requirement.uninstall( + auto_confirm=True + ) + try: + requirement.install( + install_options, + global_options, + *args, + **kwargs + ) + except Exception: + should_rollback = ( + requirement.conflicts_with and + not requirement.install_succeeded + ) + # if install did not succeed, rollback previous uninstall + if should_rollback: + uninstalled_pathset.rollback() + raise + else: + should_commit = ( + requirement.conflicts_with and + requirement.install_succeeded + ) + if should_commit: + uninstalled_pathset.commit() + requirement.remove_temporary_source() + + return to_install diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/constructors.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/constructors.py new file mode 100644 index 0000000..1eed1dd --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/constructors.py @@ -0,0 +1,339 @@ +"""Backing implementation for InstallRequirement's various constructors + +The idea here is that these formed a major chunk of InstallRequirement's size +so, moving them and support code dedicated to them outside of that class +helps creates for better understandability for the rest of the code. + +These are meant to be used elsewhere within pip to create instances of +InstallRequirement. +""" + +import logging +import os +import re + +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.download import ( + is_archive_file, is_url, path_to_url, url_to_path, +) +from pip._internal.exceptions import InstallationError +from pip._internal.models.index import PyPI, TestPyPI +from pip._internal.models.link import Link +from pip._internal.pyproject import make_pyproject_path +from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils.misc import is_installable_dir +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.vcs import vcs +from pip._internal.wheel import Wheel + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Optional, Tuple, Set, Any, Union, Text, Dict, + ) + from pip._internal.cache import WheelCache # noqa: F401 + + +__all__ = [ + "install_req_from_editable", "install_req_from_line", + "parse_editable" +] + +logger = logging.getLogger(__name__) +operators = Specifier._operators.keys() + + +def _strip_extras(path): + # type: (str) -> Tuple[str, Optional[str]] + m = re.match(r'^(.+)(\[[^\]]+\])$', path) + extras = None + if m: + path_no_extras = m.group(1) + extras = m.group(2) + else: + path_no_extras = path + + return path_no_extras, extras + + +def parse_editable(editable_req): + # type: (str) -> Tuple[Optional[str], str, Optional[Set[str]]] + """Parses an editable requirement into: + - a requirement name + - an URL + - extras + - editable options + Accepted requirements: + svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir + .[some_extra] + """ + + url = editable_req + + # If a file path is specified with extras, strip off the extras. + url_no_extras, extras = _strip_extras(url) + + if os.path.isdir(url_no_extras): + if not os.path.exists(os.path.join(url_no_extras, 'setup.py')): + msg = ( + 'File "setup.py" not found. Directory cannot be installed ' + 'in editable mode: {}'.format(os.path.abspath(url_no_extras)) + ) + pyproject_path = make_pyproject_path(url_no_extras) + if os.path.isfile(pyproject_path): + msg += ( + '\n(A "pyproject.toml" file was found, but editable ' + 'mode currently requires a setup.py based build.)' + ) + raise InstallationError(msg) + + # Treating it as code that has already been checked out + url_no_extras = path_to_url(url_no_extras) + + if url_no_extras.lower().startswith('file:'): + package_name = Link(url_no_extras).egg_fragment + if extras: + return ( + package_name, + url_no_extras, + Requirement("placeholder" + extras.lower()).extras, + ) + else: + return package_name, url_no_extras, None + + for version_control in vcs: + if url.lower().startswith('%s:' % version_control): + url = '%s+%s' % (version_control, url) + break + + if '+' not in url: + raise InstallationError( + '%s should either be a path to a local project or a VCS url ' + 'beginning with svn+, git+, hg+, or bzr+' % + editable_req + ) + + vc_type = url.split('+', 1)[0].lower() + + if not vcs.get_backend(vc_type): + error_message = 'For --editable=%s only ' % editable_req + \ + ', '.join([backend.name + '+URL' for backend in vcs.backends]) + \ + ' is currently supported' + raise InstallationError(error_message) + + package_name = Link(url).egg_fragment + if not package_name: + raise InstallationError( + "Could not detect requirement name for '%s', please specify one " + "with #egg=your_package_name" % editable_req + ) + return package_name, url, None + + +def deduce_helpful_msg(req): + # type: (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 = " It does exist." + # Try to parse and check if it is a requirements file. + try: + with open(req, 'r') as fp: + # parse first line only + next(parse_requirements(fp.read())) + msg += " The argument you provided " + \ + "(%s) appears to be a" % (req) + \ + " requirements file. If that is the" + \ + " case, use the '-r' flag to install" + \ + " the packages specified within it." + except RequirementParseError: + logger.debug("Cannot parse '%s' as requirements \ + file" % (req), exc_info=True) + else: + msg += " File '%s' does not exist." % (req) + return msg + + +# ---- The actual constructors follow ---- + + +def install_req_from_editable( + editable_req, # type: str + comes_from=None, # type: Optional[str] + use_pep517=None, # type: Optional[bool] + isolated=False, # type: bool + options=None, # type: Optional[Dict[str, Any]] + wheel_cache=None, # type: Optional[WheelCache] + constraint=False # type: bool +): + # type: (...) -> InstallRequirement + name, url, extras_override = parse_editable(editable_req) + if url.startswith('file:'): + source_dir = url_to_path(url) + else: + source_dir = None + + if name is not None: + try: + req = Requirement(name) + except InvalidRequirement: + raise InstallationError("Invalid requirement: '%s'" % name) + else: + req = None + return InstallRequirement( + req, comes_from, source_dir=source_dir, + editable=True, + link=Link(url), + constraint=constraint, + use_pep517=use_pep517, + isolated=isolated, + options=options if options else {}, + wheel_cache=wheel_cache, + extras=extras_override or (), + ) + + +def install_req_from_line( + name, # type: str + comes_from=None, # type: Optional[Union[str, InstallRequirement]] + use_pep517=None, # type: Optional[bool] + isolated=False, # type: bool + options=None, # type: Optional[Dict[str, Any]] + wheel_cache=None, # type: Optional[WheelCache] + constraint=False # type: bool +): + # type: (...) -> InstallRequirement + """Creates an InstallRequirement from a name, which might be a + requirement, directory containing 'setup.py', filename, or URL. + """ + if is_url(name): + marker_sep = '; ' + else: + marker_sep = ';' + if marker_sep in name: + name, markers_as_string = name.split(marker_sep, 1) + markers_as_string = markers_as_string.strip() + if not markers_as_string: + markers = None + else: + markers = Marker(markers_as_string) + else: + markers = None + name = name.strip() + req_as_string = None + path = os.path.normpath(os.path.abspath(name)) + link = None + extras_as_string = None + + if is_url(name): + link = Link(name) + else: + p, extras_as_string = _strip_extras(path) + looks_like_dir = os.path.isdir(p) and ( + os.path.sep in name or + (os.path.altsep is not None and os.path.altsep in name) or + name.startswith('.') + ) + if looks_like_dir: + if not is_installable_dir(p): + raise InstallationError( + "Directory %r is not installable. Neither 'setup.py' " + "nor 'pyproject.toml' found." % name + ) + link = Link(path_to_url(p)) + elif is_archive_file(p): + if not os.path.isfile(p): + logger.warning( + 'Requirement %r looks like a filename, but the ' + 'file does not exist', + name + ) + link = Link(path_to_url(p)) + + # it's a local file, dir, or url + if link: + # Handle relative file URLs + if link.scheme == 'file' and re.search(r'\.\./', link.url): + link = Link( + path_to_url(os.path.normpath(os.path.abspath(link.path)))) + # wheel file + if link.is_wheel: + wheel = Wheel(link.filename) # can raise InvalidWheelFilename + req_as_string = "%s==%s" % (wheel.name, wheel.version) + else: + # set the req to the egg fragment. when it's not there, this + # will become an 'unnamed' requirement + req_as_string = link.egg_fragment + + # a requirement specifier + else: + req_as_string = name + + if extras_as_string: + extras = Requirement("placeholder" + extras_as_string.lower()).extras + else: + extras = () + if req_as_string is not None: + try: + req = Requirement(req_as_string) + except InvalidRequirement: + if os.path.sep in req_as_string: + add_msg = "It looks like a path." + add_msg += deduce_helpful_msg(req_as_string) + elif ('=' in req_as_string and + not any(op in req_as_string for op in operators)): + add_msg = "= is not a valid operator. Did you mean == ?" + else: + add_msg = "" + raise InstallationError( + "Invalid requirement: '%s'\n%s" % (req_as_string, add_msg) + ) + else: + req = None + + return InstallRequirement( + req, comes_from, link=link, markers=markers, + use_pep517=use_pep517, isolated=isolated, + options=options if options else {}, + wheel_cache=wheel_cache, + constraint=constraint, + extras=extras, + ) + + +def install_req_from_req_string( + req_string, # type: str + comes_from=None, # type: Optional[InstallRequirement] + isolated=False, # type: bool + wheel_cache=None, # type: Optional[WheelCache] + use_pep517=None # type: Optional[bool] +): + # type: (...) -> InstallRequirement + try: + req = Requirement(req_string) + except InvalidRequirement: + raise InstallationError("Invalid requirement: '%s'" % req) + + domains_not_allowed = [ + PyPI.file_storage_domain, + TestPyPI.file_storage_domain, + ] + if req.url and comes_from.link.netloc in domains_not_allowed: + # Explicitly disallow pypi packages that depend on external urls + raise InstallationError( + "Packages installed from PyPI cannot depend on packages " + "which are not also hosted on PyPI.\n" + "%s depends on %s " % (comes_from.name, req) + ) + + return InstallRequirement( + req, comes_from, isolated=isolated, wheel_cache=wheel_cache, + use_pep517=use_pep517 + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_file.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_file.py new file mode 100644 index 0000000..726f2f6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_file.py @@ -0,0 +1,382 @@ +""" +Requirements file parsing +""" + +from __future__ import absolute_import + +import optparse +import os +import re +import shlex +import sys + +from pip._vendor.six.moves import filterfalse +from pip._vendor.six.moves.urllib import parse as urllib_parse + +from pip._internal.cli import cmdoptions +from pip._internal.download import get_file_content +from pip._internal.exceptions import RequirementsFileParseError +from pip._internal.req.constructors import ( + install_req_from_editable, install_req_from_line, +) +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Iterator, Tuple, Optional, List, Callable, Text + ) + from pip._internal.req import InstallRequirement # noqa: F401 + from pip._internal.cache import WheelCache # noqa: F401 + from pip._internal.index import PackageFinder # noqa: F401 + from pip._internal.download import PipSession # noqa: F401 + + ReqFileLines = Iterator[Tuple[int, Text]] + +__all__ = ['parse_requirements'] + +SCHEME_RE = re.compile(r'^(http|https|file):', re.I) +COMMENT_RE = re.compile(r'(^|\s)+#.*$') + +# Matches environment variable-style values in '${MY_VARIABLE_1}' with the +# variable name consisting of only uppercase letters, digits or the '_' +# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1, +# 2013 Edition. +ENV_VAR_RE = re.compile(r'(?P\$\{(?P[A-Z0-9_]+)\})') + +SUPPORTED_OPTIONS = [ + cmdoptions.constraints, + cmdoptions.editable, + cmdoptions.requirements, + cmdoptions.no_index, + cmdoptions.index_url, + cmdoptions.find_links, + cmdoptions.extra_index_url, + cmdoptions.always_unzip, + cmdoptions.no_binary, + cmdoptions.only_binary, + cmdoptions.pre, + cmdoptions.trusted_host, + cmdoptions.require_hashes, +] # type: List[Callable[..., optparse.Option]] + +# options to be passed to requirements +SUPPORTED_OPTIONS_REQ = [ + cmdoptions.install_options, + cmdoptions.global_options, + cmdoptions.hash, +] # type: List[Callable[..., optparse.Option]] + +# the 'dest' string values +SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ] + + +def parse_requirements( + filename, # type: str + finder=None, # type: Optional[PackageFinder] + comes_from=None, # type: Optional[str] + options=None, # type: Optional[optparse.Values] + session=None, # type: Optional[PipSession] + constraint=False, # type: bool + wheel_cache=None, # type: Optional[WheelCache] + use_pep517=None # type: Optional[bool] +): + # type: (...) -> Iterator[InstallRequirement] + """Parse a requirements file and yield InstallRequirement instances. + + :param filename: Path or url of requirements file. + :param finder: Instance of pip.index.PackageFinder. + :param comes_from: Origin description of requirements. + :param options: cli options. + :param session: Instance of pip.download.PipSession. + :param constraint: If true, parsing a constraint file rather than + requirements file. + :param wheel_cache: Instance of pip.wheel.WheelCache + :param use_pep517: Value of the --use-pep517 option. + """ + if session is None: + raise TypeError( + "parse_requirements() missing 1 required keyword argument: " + "'session'" + ) + + _, content = get_file_content( + filename, comes_from=comes_from, session=session + ) + + lines_enum = preprocess(content, options) + + for line_number, line in lines_enum: + req_iter = process_line(line, filename, line_number, finder, + comes_from, options, session, wheel_cache, + use_pep517=use_pep517, constraint=constraint) + for req in req_iter: + yield req + + +def preprocess(content, options): + # type: (Text, Optional[optparse.Values]) -> ReqFileLines + """Split, filter, and join lines, and return a line iterator + + :param content: the content of the requirements file + :param options: cli options + """ + lines_enum = enumerate(content.splitlines(), start=1) # type: ReqFileLines + lines_enum = join_lines(lines_enum) + lines_enum = ignore_comments(lines_enum) + lines_enum = skip_regex(lines_enum, options) + lines_enum = expand_env_variables(lines_enum) + return lines_enum + + +def process_line( + line, # type: Text + filename, # type: str + line_number, # type: int + finder=None, # type: Optional[PackageFinder] + comes_from=None, # type: Optional[str] + options=None, # type: Optional[optparse.Values] + session=None, # type: Optional[PipSession] + wheel_cache=None, # type: Optional[WheelCache] + use_pep517=None, # type: Optional[bool] + constraint=False # type: bool +): + # type: (...) -> Iterator[InstallRequirement] + """Process a single requirements line; This can result in creating/yielding + requirements, or updating the finder. + + For lines that contain requirements, the only options that have an effect + are from SUPPORTED_OPTIONS_REQ, and they are scoped to the + requirement. Other options from SUPPORTED_OPTIONS may be present, but are + ignored. + + For lines that do not contain requirements, the only options that have an + effect are from SUPPORTED_OPTIONS. Options from SUPPORTED_OPTIONS_REQ may + be present, but are ignored. These lines may contain multiple options + (although our docs imply only one is supported), and all our parsed and + affect the finder. + + :param constraint: If True, parsing a constraints file. + :param options: OptionParser options that we may update + """ + parser = build_parser(line) + defaults = parser.get_default_values() + defaults.index_url = None + if finder: + defaults.format_control = finder.format_control + args_str, options_str = break_args_options(line) + # Prior to 2.7.3, shlex cannot deal with unicode entries + if sys.version_info < (2, 7, 3): + # https://github.com/python/mypy/issues/1174 + options_str = options_str.encode('utf8') # type: ignore + # https://github.com/python/mypy/issues/1174 + opts, _ = parser.parse_args( + shlex.split(options_str), defaults) # type: ignore + + # preserve for the nested code path + line_comes_from = '%s %s (line %s)' % ( + '-c' if constraint else '-r', filename, line_number, + ) + + # yield a line requirement + if args_str: + isolated = options.isolated_mode if options else False + if options: + cmdoptions.check_install_build_global(options, opts) + # get the options that apply to requirements + req_options = {} + for dest in SUPPORTED_OPTIONS_REQ_DEST: + if dest in opts.__dict__ and opts.__dict__[dest]: + req_options[dest] = opts.__dict__[dest] + yield install_req_from_line( + args_str, line_comes_from, constraint=constraint, + use_pep517=use_pep517, + isolated=isolated, options=req_options, wheel_cache=wheel_cache + ) + + # yield an editable requirement + elif opts.editables: + isolated = options.isolated_mode if options else False + yield install_req_from_editable( + opts.editables[0], comes_from=line_comes_from, + use_pep517=use_pep517, + constraint=constraint, isolated=isolated, wheel_cache=wheel_cache + ) + + # parse a nested requirements file + elif opts.requirements or opts.constraints: + if opts.requirements: + req_path = opts.requirements[0] + nested_constraint = False + else: + req_path = opts.constraints[0] + nested_constraint = True + # original file is over http + if SCHEME_RE.search(filename): + # do a url join so relative paths work + req_path = urllib_parse.urljoin(filename, req_path) + # original file and nested file are paths + elif not SCHEME_RE.search(req_path): + # do a join so relative paths work + req_path = os.path.join(os.path.dirname(filename), req_path) + # TODO: Why not use `comes_from='-r {} (line {})'` here as well? + parsed_reqs = parse_requirements( + req_path, finder, comes_from, options, session, + constraint=nested_constraint, wheel_cache=wheel_cache + ) + for req in parsed_reqs: + yield req + + # percolate hash-checking option upward + elif opts.require_hashes: + options.require_hashes = opts.require_hashes + + # set finder options + elif finder: + if opts.index_url: + finder.index_urls = [opts.index_url] + if opts.no_index is True: + finder.index_urls = [] + if opts.extra_index_urls: + finder.index_urls.extend(opts.extra_index_urls) + if opts.find_links: + # FIXME: it would be nice to keep track of the source + # of the find_links: support a find-links local path + # relative to a requirements file. + value = opts.find_links[0] + req_dir = os.path.dirname(os.path.abspath(filename)) + relative_to_reqs_file = os.path.join(req_dir, value) + if os.path.exists(relative_to_reqs_file): + value = relative_to_reqs_file + finder.find_links.append(value) + if opts.pre: + finder.allow_all_prereleases = True + if opts.trusted_hosts: + finder.secure_origins.extend( + ("*", host, "*") for host in opts.trusted_hosts) + + +def break_args_options(line): + # type: (Text) -> Tuple[str, Text] + """Break up the line into an args and options string. We only want to shlex + (and then optparse) the options, not the args. args can contain markers + which are corrupted by shlex. + """ + tokens = line.split(' ') + args = [] + options = tokens[:] + for token in tokens: + if token.startswith('-') or token.startswith('--'): + break + else: + args.append(token) + options.pop(0) + return ' '.join(args), ' '.join(options) # type: ignore + + +def build_parser(line): + # type: (Text) -> optparse.OptionParser + """ + Return a parser for parsing requirement lines + """ + parser = optparse.OptionParser(add_help_option=False) + + option_factories = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ + for option_factory in option_factories: + option = option_factory() + parser.add_option(option) + + # By default optparse sys.exits on parsing errors. We want to wrap + # that in our own exception. + def parser_exit(self, msg): + # add offending line + msg = 'Invalid requirement: %s\n%s' % (line, msg) + raise RequirementsFileParseError(msg) + # NOTE: mypy disallows assigning to a method + # https://github.com/python/mypy/issues/2427 + parser.exit = parser_exit # type: ignore + + return parser + + +def join_lines(lines_enum): + # type: (ReqFileLines) -> ReqFileLines + """Joins a line ending in '\' with the previous line (except when following + comments). The joined line takes on the index of the first line. + """ + primary_line_number = None + new_line = [] # type: List[Text] + for line_number, line in lines_enum: + if not line.endswith('\\') or COMMENT_RE.match(line): + if COMMENT_RE.match(line): + # this ensures comments are always matched later + line = ' ' + line + if new_line: + new_line.append(line) + yield primary_line_number, ''.join(new_line) + new_line = [] + else: + yield line_number, line + else: + if not new_line: + primary_line_number = line_number + new_line.append(line.strip('\\')) + + # last line contains \ + if new_line: + yield primary_line_number, ''.join(new_line) + + # TODO: handle space after '\'. + + +def ignore_comments(lines_enum): + # type: (ReqFileLines) -> ReqFileLines + """ + Strips comments and filter empty lines. + """ + for line_number, line in lines_enum: + line = COMMENT_RE.sub('', line) + line = line.strip() + if line: + yield line_number, line + + +def skip_regex(lines_enum, options): + # type: (ReqFileLines, Optional[optparse.Values]) -> ReqFileLines + """ + Skip lines that match '--skip-requirements-regex' pattern + + Note: the regex pattern is only built once + """ + skip_regex = options.skip_requirements_regex if options else None + if skip_regex: + pattern = re.compile(skip_regex) + lines_enum = filterfalse(lambda e: pattern.search(e[1]), lines_enum) + return lines_enum + + +def expand_env_variables(lines_enum): + # type: (ReqFileLines) -> ReqFileLines + """Replace all environment variables that can be retrieved via `os.getenv`. + + The only allowed format for environment variables defined in the + requirement file is `${MY_VARIABLE_1}` to ensure two things: + + 1. Strings that contain a `$` aren't accidentally (partially) expanded. + 2. Ensure consistency across platforms for requirement files. + + These points are the result of a discusssion on the `github pull + request #3514 `_. + + Valid characters in variable names follow the `POSIX standard + `_ and are limited + to uppercase letter, digits and the `_` (underscore). + """ + for line_number, line in lines_enum: + for env_var, var_name in ENV_VAR_RE.findall(line): + value = os.getenv(var_name) + if not value: + continue + + line = line.replace(env_var, value) + + yield line_number, line diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_install.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_install.py new file mode 100644 index 0000000..a4834b0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_install.py @@ -0,0 +1,1021 @@ +from __future__ import absolute_import + +import logging +import os +import shutil +import sys +import sysconfig +import zipfile +from distutils.util import change_root + +from pip._vendor import pkg_resources, six +from pip._vendor.packaging.requirements import Requirement +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._internal import wheel +from pip._internal.build_env import NoOpBuildEnvironment +from pip._internal.exceptions import InstallationError +from pip._internal.locations import ( + PIP_DELETE_MARKER_FILENAME, running_under_virtualenv, +) +from pip._internal.models.link import Link +from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path +from pip._internal.req.req_uninstall import UninstallPathSet +from pip._internal.utils.compat import native_str +from pip._internal.utils.hashes import Hashes +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + _make_build_dir, ask_path_exists, backup_dir, call_subprocess, + display_path, dist_in_site_packages, dist_in_usersite, ensure_dir, + get_installed_version, redact_password_from_url, rmtree, +) +from pip._internal.utils.packaging import get_metadata +from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.utils.ui import open_spinner +from pip._internal.vcs import vcs +from pip._internal.wheel import move_wheel_files + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Optional, Iterable, List, Union, Any, Text, Sequence, Dict + ) + from pip._internal.build_env import BuildEnvironment # noqa: F401 + from pip._internal.cache import WheelCache # noqa: F401 + from pip._internal.index import PackageFinder # noqa: F401 + from pip._vendor.pkg_resources import Distribution # noqa: F401 + from pip._vendor.packaging.specifiers import SpecifierSet # noqa: F401 + from pip._vendor.packaging.markers import Marker # noqa: F401 + + +logger = logging.getLogger(__name__) + + +class InstallRequirement(object): + """ + Represents something that may be installed later on, may have information + about where to fetch the relavant requirement and also contains logic for + installing the said requirement. + """ + + def __init__( + self, + req, # type: Optional[Requirement] + comes_from, # type: Optional[Union[str, InstallRequirement]] + source_dir=None, # type: Optional[str] + editable=False, # type: bool + link=None, # type: Optional[Link] + update=True, # type: bool + markers=None, # type: Optional[Marker] + use_pep517=None, # type: Optional[bool] + isolated=False, # type: bool + options=None, # type: Optional[Dict[str, Any]] + wheel_cache=None, # type: Optional[WheelCache] + constraint=False, # type: bool + extras=() # type: Iterable[str] + ): + # type: (...) -> None + assert req is None or isinstance(req, Requirement), req + self.req = req + self.comes_from = comes_from + self.constraint = constraint + if source_dir is not None: + self.source_dir = os.path.normpath(os.path.abspath(source_dir)) + else: + self.source_dir = None + self.editable = editable + + self._wheel_cache = wheel_cache + if link is None and req and req.url: + # PEP 508 URL requirement + link = Link(req.url) + self.link = self.original_link = link + + if extras: + self.extras = extras + elif req: + self.extras = { + pkg_resources.safe_extra(extra) for extra in req.extras + } + else: + self.extras = set() + if markers is None and req: + markers = req.marker + self.markers = markers + + self._egg_info_path = None # type: Optional[str] + # This holds the pkg_resources.Distribution object if this requirement + # is already available: + self.satisfied_by = None + # This hold the pkg_resources.Distribution object if this requirement + # conflicts with another installed distribution: + self.conflicts_with = None + # Temporary build location + self._temp_build_dir = TempDirectory(kind="req-build") + # Used to store the global directory where the _temp_build_dir should + # have been created. Cf _correct_build_location method. + self._ideal_build_dir = None # type: Optional[str] + # True if the editable should be updated: + self.update = update + # Set to True after successful installation + self.install_succeeded = None # type: Optional[bool] + # UninstallPathSet of uninstalled distribution (for possible rollback) + self.uninstalled_pathset = None + self.options = options if options else {} + # Set to True after successful preparation of this requirement + self.prepared = False + self.is_direct = False + + self.isolated = isolated + self.build_env = NoOpBuildEnvironment() # type: BuildEnvironment + + # For PEP 517, the directory where we request the project metadata + # gets stored. We need this to pass to build_wheel, so the backend + # can ensure that the wheel matches the metadata (see the PEP for + # details). + self.metadata_directory = None # type: Optional[str] + + # The static build requirements (from pyproject.toml) + self.pyproject_requires = None # type: Optional[List[str]] + + # Build requirements that we will check are available + self.requirements_to_check = [] # type: List[str] + + # The PEP 517 backend we should use to build the project + self.pep517_backend = None # type: Optional[Pep517HookCaller] + + # Are we using PEP 517 for this requirement? + # After pyproject.toml has been loaded, the only valid values are True + # and False. Before loading, None is valid (meaning "use the default"). + # Setting an explicit value before loading pyproject.toml is supported, + # but after loading this flag should be treated as read only. + self.use_pep517 = use_pep517 + + def __str__(self): + if self.req: + s = str(self.req) + if self.link: + s += ' from %s' % redact_password_from_url(self.link.url) + elif self.link: + s = redact_password_from_url(self.link.url) + else: + s = '' + if self.satisfied_by is not None: + s += ' in %s' % display_path(self.satisfied_by.location) + if self.comes_from: + if isinstance(self.comes_from, six.string_types): + comes_from = self.comes_from + else: + comes_from = self.comes_from.from_path() + if comes_from: + s += ' (from %s)' % comes_from + return s + + def __repr__(self): + return '<%s object: %s editable=%r>' % ( + self.__class__.__name__, str(self), self.editable) + + def populate_link(self, finder, upgrade, require_hashes): + # type: (PackageFinder, bool, bool) -> None + """Ensure that if a link can be found for this, that it is found. + + Note that self.link may still be None - if Upgrade is False and the + requirement is already installed. + + If require_hashes is True, don't use the wheel cache, because cached + wheels, always built locally, have different hashes than the files + downloaded from the index server and thus throw false hash mismatches. + Furthermore, cached wheels at present have undeterministic contents due + to file modification times. + """ + if self.link is None: + self.link = finder.find_requirement(self, upgrade) + if self._wheel_cache is not None and not require_hashes: + old_link = self.link + self.link = self._wheel_cache.get(self.link, self.name) + if old_link != self.link: + logger.debug('Using cached wheel link: %s', self.link) + + # Things that are valid for all kinds of requirements? + @property + def name(self): + # type: () -> Optional[str] + if self.req is None: + return None + return native_str(pkg_resources.safe_name(self.req.name)) + + @property + def specifier(self): + # type: () -> SpecifierSet + return self.req.specifier + + @property + def is_pinned(self): + # type: () -> bool + """Return whether I am pinned to an exact version. + + For example, some-package==1.2 is pinned; some-package>1.2 is not. + """ + specifiers = self.specifier + return (len(specifiers) == 1 and + next(iter(specifiers)).operator in {'==', '==='}) + + @property + def installed_version(self): + return get_installed_version(self.name) + + def match_markers(self, extras_requested=None): + # type: (Optional[Iterable[str]]) -> bool + if not extras_requested: + # Provide an extra to safely evaluate the markers + # without matching any extra + extras_requested = ('',) + if self.markers is not None: + return any( + self.markers.evaluate({'extra': extra}) + for extra in extras_requested) + else: + return True + + @property + def has_hash_options(self): + # type: () -> bool + """Return whether any known-good hashes are specified as options. + + These activate --require-hashes mode; hashes specified as part of a + URL do not. + + """ + return bool(self.options.get('hashes', {})) + + def hashes(self, trust_internet=True): + # type: (bool) -> Hashes + """Return a hash-comparer that considers my option- and URL-based + hashes to be known-good. + + Hashes in URLs--ones embedded in the requirements file, not ones + downloaded from an index server--are almost peers with ones from + flags. They satisfy --require-hashes (whether it was implicitly or + explicitly activated) but do not activate it. md5 and sha224 are not + allowed in flags, which should nudge people toward good algos. We + always OR all hashes together, even ones from URLs. + + :param trust_internet: Whether to trust URL-based (#md5=...) hashes + downloaded from the internet, as by populate_link() + + """ + good_hashes = self.options.get('hashes', {}).copy() + link = self.link if trust_internet else self.original_link + if link and link.hash: + good_hashes.setdefault(link.hash_name, []).append(link.hash) + return Hashes(good_hashes) + + def from_path(self): + # type: () -> Optional[str] + """Format a nice indicator to show where this "comes from" + """ + if self.req is None: + return None + s = str(self.req) + if self.comes_from: + if isinstance(self.comes_from, six.string_types): + comes_from = self.comes_from + else: + comes_from = self.comes_from.from_path() + if comes_from: + s += '->' + comes_from + return s + + def build_location(self, build_dir): + # type: (str) -> Optional[str] + assert build_dir is not None + if self._temp_build_dir.path is not None: + return self._temp_build_dir.path + if self.req is None: + # for requirement via a path to a directory: the name of the + # package is not available yet so we create a temp directory + # Once run_egg_info will have run, we'll be able + # to fix it via _correct_build_location + # Some systems have /tmp as a symlink which confuses custom + # builds (such as numpy). Thus, we ensure that the real path + # is returned. + self._temp_build_dir.create() + self._ideal_build_dir = build_dir + + return self._temp_build_dir.path + if self.editable: + name = self.name.lower() + else: + name = self.name + # FIXME: Is there a better place to create the build_dir? (hg and bzr + # need this) + if not os.path.exists(build_dir): + logger.debug('Creating directory %s', build_dir) + _make_build_dir(build_dir) + return os.path.join(build_dir, name) + + def _correct_build_location(self): + # type: () -> None + """Move self._temp_build_dir to self._ideal_build_dir/self.req.name + + For some requirements (e.g. a path to a directory), the name of the + package is not available until we run egg_info, so the build_location + will return a temporary directory and store the _ideal_build_dir. + + This is only called by self.run_egg_info to fix the temporary build + directory. + """ + if self.source_dir is not None: + return + assert self.req is not None + assert self._temp_build_dir.path + assert (self._ideal_build_dir is not None and + self._ideal_build_dir.path) # type: ignore + old_location = self._temp_build_dir.path + self._temp_build_dir.path = None + + new_location = self.build_location(self._ideal_build_dir) + if os.path.exists(new_location): + raise InstallationError( + 'A package already exists in %s; please remove it to continue' + % display_path(new_location)) + logger.debug( + 'Moving package %s from %s to new location %s', + self, display_path(old_location), display_path(new_location), + ) + shutil.move(old_location, new_location) + self._temp_build_dir.path = new_location + self._ideal_build_dir = None + self.source_dir = os.path.normpath(os.path.abspath(new_location)) + self._egg_info_path = None + + # Correct the metadata directory, if it exists + if self.metadata_directory: + old_meta = self.metadata_directory + rel = os.path.relpath(old_meta, start=old_location) + new_meta = os.path.join(new_location, rel) + new_meta = os.path.normpath(os.path.abspath(new_meta)) + self.metadata_directory = new_meta + + def remove_temporary_source(self): + # type: () -> None + """Remove the source files from this requirement, if they are marked + for deletion""" + if self.source_dir and os.path.exists( + os.path.join(self.source_dir, PIP_DELETE_MARKER_FILENAME)): + logger.debug('Removing source in %s', self.source_dir) + rmtree(self.source_dir) + self.source_dir = None + self._temp_build_dir.cleanup() + self.build_env.cleanup() + + def check_if_exists(self, use_user_site): + # type: (bool) -> bool + """Find an installed distribution that satisfies or conflicts + with this requirement, and set self.satisfied_by or + self.conflicts_with appropriately. + """ + if self.req is None: + return False + try: + # get_distribution() will resolve the entire list of requirements + # anyway, and we've already determined that we need the requirement + # in question, so strip the marker so that we don't try to + # evaluate it. + no_marker = Requirement(str(self.req)) + no_marker.marker = None + self.satisfied_by = pkg_resources.get_distribution(str(no_marker)) + if self.editable and self.satisfied_by: + self.conflicts_with = self.satisfied_by + # when installing editables, nothing pre-existing should ever + # satisfy + self.satisfied_by = None + return True + except pkg_resources.DistributionNotFound: + return False + except pkg_resources.VersionConflict: + existing_dist = pkg_resources.get_distribution( + self.req.name + ) + if use_user_site: + if dist_in_usersite(existing_dist): + self.conflicts_with = existing_dist + elif (running_under_virtualenv() and + dist_in_site_packages(existing_dist)): + raise InstallationError( + "Will not install to the user site because it will " + "lack sys.path precedence to %s in %s" % + (existing_dist.project_name, existing_dist.location) + ) + else: + self.conflicts_with = existing_dist + return True + + # Things valid for wheels + @property + def is_wheel(self): + # type: () -> bool + if not self.link: + return False + return self.link.is_wheel + + def move_wheel_files( + self, + wheeldir, # type: str + root=None, # type: Optional[str] + home=None, # type: Optional[str] + prefix=None, # type: Optional[str] + warn_script_location=True, # type: bool + use_user_site=False, # type: bool + pycompile=True # type: bool + ): + # type: (...) -> None + move_wheel_files( + self.name, self.req, wheeldir, + user=use_user_site, + home=home, + root=root, + prefix=prefix, + pycompile=pycompile, + isolated=self.isolated, + warn_script_location=warn_script_location, + ) + + # Things valid for sdists + @property + def setup_py_dir(self): + # type: () -> str + return os.path.join( + self.source_dir, + self.link and self.link.subdirectory_fragment or '') + + @property + def setup_py(self): + # type: () -> str + assert self.source_dir, "No source dir for %s" % self + + setup_py = os.path.join(self.setup_py_dir, 'setup.py') + + # Python2 __file__ should not be unicode + if six.PY2 and isinstance(setup_py, six.text_type): + setup_py = setup_py.encode(sys.getfilesystemencoding()) + + return setup_py + + @property + def pyproject_toml(self): + # type: () -> str + assert self.source_dir, "No source dir for %s" % self + + return make_pyproject_path(self.setup_py_dir) + + def load_pyproject_toml(self): + # type: () -> None + """Load the pyproject.toml file. + + After calling this routine, all of the attributes related to PEP 517 + processing for this requirement have been set. In particular, the + use_pep517 attribute can be used to determine whether we should + follow the PEP 517 or legacy (setup.py) code path. + """ + pep517_data = load_pyproject_toml( + self.use_pep517, + self.pyproject_toml, + self.setup_py, + str(self) + ) + + if pep517_data is None: + self.use_pep517 = False + else: + self.use_pep517 = True + requires, backend, check = pep517_data + self.requirements_to_check = check + self.pyproject_requires = requires + self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend) + + # Use a custom function to call subprocesses + self.spin_message = "" + + def runner(cmd, cwd=None, extra_environ=None): + with open_spinner(self.spin_message) as spinner: + call_subprocess( + cmd, + cwd=cwd, + extra_environ=extra_environ, + show_stdout=False, + spinner=spinner + ) + self.spin_message = "" + + self.pep517_backend._subprocess_runner = runner + + def prepare_metadata(self): + # type: () -> None + """Ensure that project metadata is available. + + Under PEP 517, call the backend hook to prepare the metadata. + Under legacy processing, call setup.py egg-info. + """ + assert self.source_dir + + with indent_log(): + if self.use_pep517: + self.prepare_pep517_metadata() + else: + self.run_egg_info() + + if not self.req: + if isinstance(parse_version(self.metadata["Version"]), Version): + op = "==" + else: + op = "===" + self.req = Requirement( + "".join([ + self.metadata["Name"], + op, + self.metadata["Version"], + ]) + ) + self._correct_build_location() + else: + metadata_name = canonicalize_name(self.metadata["Name"]) + if canonicalize_name(self.req.name) != metadata_name: + logger.warning( + 'Generating metadata for package %s ' + 'produced metadata for project name %s. Fix your ' + '#egg=%s fragments.', + self.name, metadata_name, self.name + ) + self.req = Requirement(metadata_name) + + def prepare_pep517_metadata(self): + # type: () -> None + assert self.pep517_backend is not None + + metadata_dir = os.path.join( + self.setup_py_dir, + 'pip-wheel-metadata' + ) + ensure_dir(metadata_dir) + + with self.build_env: + # Note that Pep517HookCaller implements a fallback for + # prepare_metadata_for_build_wheel, so we don't have to + # consider the possibility that this hook doesn't exist. + backend = self.pep517_backend + self.spin_message = "Preparing wheel metadata" + distinfo_dir = backend.prepare_metadata_for_build_wheel( + metadata_dir + ) + + self.metadata_directory = os.path.join(metadata_dir, distinfo_dir) + + def run_egg_info(self): + # type: () -> None + if self.name: + logger.debug( + 'Running setup.py (path:%s) egg_info for package %s', + self.setup_py, self.name, + ) + else: + logger.debug( + 'Running setup.py (path:%s) egg_info for package from %s', + self.setup_py, self.link, + ) + script = SETUPTOOLS_SHIM % self.setup_py + base_cmd = [sys.executable, '-c', script] + if self.isolated: + base_cmd += ["--no-user-cfg"] + egg_info_cmd = base_cmd + ['egg_info'] + # We can't put the .egg-info files at the root, because then the + # source code will be mistaken for an installed egg, causing + # problems + if self.editable: + egg_base_option = [] # type: List[str] + else: + egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info') + ensure_dir(egg_info_dir) + egg_base_option = ['--egg-base', 'pip-egg-info'] + with self.build_env: + call_subprocess( + egg_info_cmd + egg_base_option, + cwd=self.setup_py_dir, + show_stdout=False, + command_desc='python setup.py egg_info') + + @property + def egg_info_path(self): + # type: () -> str + if self._egg_info_path is None: + if self.editable: + base = self.source_dir + else: + base = os.path.join(self.setup_py_dir, 'pip-egg-info') + filenames = os.listdir(base) + if self.editable: + filenames = [] + for root, dirs, files in os.walk(base): + for dir in vcs.dirnames: + if dir in dirs: + dirs.remove(dir) + # Iterate over a copy of ``dirs``, since mutating + # a list while iterating over it can cause trouble. + # (See https://github.com/pypa/pip/pull/462.) + for dir in list(dirs): + # Don't search in anything that looks like a virtualenv + # environment + if ( + os.path.lexists( + os.path.join(root, dir, 'bin', 'python') + ) or + os.path.exists( + os.path.join( + root, dir, 'Scripts', 'Python.exe' + ) + )): + dirs.remove(dir) + # Also don't search through tests + elif dir == 'test' or dir == 'tests': + dirs.remove(dir) + filenames.extend([os.path.join(root, dir) + for dir in dirs]) + filenames = [f for f in filenames if f.endswith('.egg-info')] + + if not filenames: + raise InstallationError( + "Files/directories not found in %s" % base + ) + # if we have more than one match, we pick the toplevel one. This + # can easily be the case if there is a dist folder which contains + # an extracted tarball for testing purposes. + if len(filenames) > 1: + filenames.sort( + key=lambda x: x.count(os.path.sep) + + (os.path.altsep and x.count(os.path.altsep) or 0) + ) + self._egg_info_path = os.path.join(base, filenames[0]) + return self._egg_info_path + + @property + def metadata(self): + if not hasattr(self, '_metadata'): + self._metadata = get_metadata(self.get_dist()) + + return self._metadata + + def get_dist(self): + # type: () -> Distribution + """Return a pkg_resources.Distribution for this requirement""" + if self.metadata_directory: + base_dir, distinfo = os.path.split(self.metadata_directory) + metadata = pkg_resources.PathMetadata( + base_dir, self.metadata_directory + ) + dist_name = os.path.splitext(distinfo)[0] + typ = pkg_resources.DistInfoDistribution + else: + egg_info = self.egg_info_path.rstrip(os.path.sep) + base_dir = os.path.dirname(egg_info) + metadata = pkg_resources.PathMetadata(base_dir, egg_info) + dist_name = os.path.splitext(os.path.basename(egg_info))[0] + # https://github.com/python/mypy/issues/1174 + typ = pkg_resources.Distribution # type: ignore + + return typ( + base_dir, + project_name=dist_name, + metadata=metadata, + ) + + def assert_source_matches_version(self): + # type: () -> None + assert self.source_dir + version = self.metadata['version'] + if self.req.specifier and version not in self.req.specifier: + logger.warning( + 'Requested %s, but installing version %s', + self, + version, + ) + else: + logger.debug( + 'Source in %s has version %s, which satisfies requirement %s', + display_path(self.source_dir), + version, + self, + ) + + # For both source distributions and editables + def ensure_has_source_dir(self, parent_dir): + # type: (str) -> str + """Ensure that a source_dir is set. + + This will create a temporary build dir if the name of the requirement + isn't known yet. + + :param parent_dir: The ideal pip parent_dir for the source_dir. + Generally src_dir for editables and build_dir for sdists. + :return: self.source_dir + """ + if self.source_dir is None: + self.source_dir = self.build_location(parent_dir) + return self.source_dir + + # For editable installations + def install_editable( + self, + install_options, # type: List[str] + global_options=(), # type: Sequence[str] + prefix=None # type: Optional[str] + ): + # type: (...) -> None + logger.info('Running setup.py develop for %s', self.name) + + if self.isolated: + global_options = list(global_options) + ["--no-user-cfg"] + + if prefix: + prefix_param = ['--prefix={}'.format(prefix)] + install_options = list(install_options) + prefix_param + + with indent_log(): + # FIXME: should we do --install-headers here too? + with self.build_env: + call_subprocess( + [ + sys.executable, + '-c', + SETUPTOOLS_SHIM % self.setup_py + ] + + list(global_options) + + ['develop', '--no-deps'] + + list(install_options), + + cwd=self.setup_py_dir, + show_stdout=False, + ) + + self.install_succeeded = True + + def update_editable(self, obtain=True): + # type: (bool) -> None + if not self.link: + logger.debug( + "Cannot update repository at %s; repository location is " + "unknown", + self.source_dir, + ) + return + assert self.editable + assert self.source_dir + if self.link.scheme == 'file': + # Static paths don't get updated + return + assert '+' in self.link.url, "bad url: %r" % self.link.url + if not self.update: + return + vc_type, url = self.link.url.split('+', 1) + backend = vcs.get_backend(vc_type) + if backend: + vcs_backend = backend(self.link.url) + if obtain: + vcs_backend.obtain(self.source_dir) + else: + vcs_backend.export(self.source_dir) + else: + assert 0, ( + 'Unexpected version control type (in %s): %s' + % (self.link, vc_type)) + + # Top-level Actions + def uninstall(self, auto_confirm=False, verbose=False, + use_user_site=False): + # type: (bool, bool, bool) -> Optional[UninstallPathSet] + """ + Uninstall the distribution currently satisfying this requirement. + + Prompts before removing or modifying files unless + ``auto_confirm`` is True. + + Refuses to delete or modify files outside of ``sys.prefix`` - + thus uninstallation within a virtual environment can only + modify that virtual environment, even if the virtualenv is + linked to global site-packages. + + """ + if not self.check_if_exists(use_user_site): + logger.warning("Skipping %s as it is not installed.", self.name) + return None + dist = self.satisfied_by or self.conflicts_with + + uninstalled_pathset = UninstallPathSet.from_dist(dist) + uninstalled_pathset.remove(auto_confirm, verbose) + return uninstalled_pathset + + def _clean_zip_name(self, name, prefix): # only used by archive. + assert name.startswith(prefix + os.path.sep), ( + "name %r doesn't start with prefix %r" % (name, prefix) + ) + name = name[len(prefix) + 1:] + name = name.replace(os.path.sep, '/') + return name + + def _get_archive_name(self, path, parentdir, rootdir): + # type: (str, str, str) -> str + path = os.path.join(parentdir, path) + name = self._clean_zip_name(path, rootdir) + return self.name + '/' + name + + # TODO: Investigate if this should be kept in InstallRequirement + # Seems to be used only when VCS + downloads + def archive(self, build_dir): + # type: (str) -> None + assert self.source_dir + create_archive = True + archive_name = '%s-%s.zip' % (self.name, self.metadata["version"]) + archive_path = os.path.join(build_dir, archive_name) + if os.path.exists(archive_path): + response = ask_path_exists( + 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)bort ' % + display_path(archive_path), ('i', 'w', 'b', 'a')) + if response == 'i': + create_archive = False + elif response == 'w': + logger.warning('Deleting %s', display_path(archive_path)) + os.remove(archive_path) + elif response == 'b': + dest_file = backup_dir(archive_path) + logger.warning( + 'Backing up %s to %s', + display_path(archive_path), + display_path(dest_file), + ) + shutil.move(archive_path, dest_file) + elif response == 'a': + sys.exit(-1) + if create_archive: + zip = zipfile.ZipFile( + archive_path, 'w', zipfile.ZIP_DEFLATED, + allowZip64=True + ) + dir = os.path.normcase(os.path.abspath(self.setup_py_dir)) + for dirpath, dirnames, filenames in os.walk(dir): + if 'pip-egg-info' in dirnames: + dirnames.remove('pip-egg-info') + for dirname in dirnames: + dir_arcname = self._get_archive_name(dirname, + parentdir=dirpath, + rootdir=dir) + zipdir = zipfile.ZipInfo(dir_arcname + '/') + zipdir.external_attr = 0x1ED << 16 # 0o755 + zip.writestr(zipdir, '') + for filename in filenames: + if filename == PIP_DELETE_MARKER_FILENAME: + continue + file_arcname = self._get_archive_name(filename, + parentdir=dirpath, + rootdir=dir) + filename = os.path.join(dirpath, filename) + zip.write(filename, file_arcname) + zip.close() + logger.info('Saved %s', display_path(archive_path)) + + def install( + self, + install_options, # type: List[str] + global_options=None, # type: Optional[Sequence[str]] + root=None, # type: Optional[str] + home=None, # type: Optional[str] + prefix=None, # type: Optional[str] + warn_script_location=True, # type: bool + use_user_site=False, # type: bool + pycompile=True # type: bool + ): + # type: (...) -> None + global_options = global_options if global_options is not None else [] + if self.editable: + self.install_editable( + install_options, global_options, prefix=prefix, + ) + return + if self.is_wheel: + version = wheel.wheel_version(self.source_dir) + wheel.check_compatibility(version, self.name) + + self.move_wheel_files( + self.source_dir, root=root, prefix=prefix, home=home, + warn_script_location=warn_script_location, + use_user_site=use_user_site, pycompile=pycompile, + ) + self.install_succeeded = True + return + + # Extend the list of global and install 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.options.get('global_options', []) + install_options = list(install_options) + \ + self.options.get('install_options', []) + + if self.isolated: + # https://github.com/python/mypy/issues/1174 + global_options = global_options + ["--no-user-cfg"] # type: ignore + + with TempDirectory(kind="record") as temp_dir: + record_filename = os.path.join(temp_dir.path, 'install-record.txt') + install_args = self.get_install_args( + global_options, record_filename, root, prefix, pycompile, + ) + msg = 'Running setup.py install for %s' % (self.name,) + with open_spinner(msg) as spinner: + with indent_log(): + with self.build_env: + call_subprocess( + install_args + install_options, + cwd=self.setup_py_dir, + show_stdout=False, + spinner=spinner, + ) + + if not os.path.exists(record_filename): + logger.debug('Record file %s not found', record_filename) + return + self.install_succeeded = True + + def prepend_root(path): + if root is None or not os.path.isabs(path): + return path + else: + return change_root(root, path) + + with open(record_filename) as f: + for line in f: + directory = os.path.dirname(line) + if directory.endswith('.egg-info'): + egg_info_dir = prepend_root(directory) + break + else: + logger.warning( + 'Could not find .egg-info directory in install record' + ' for %s', + self, + ) + # FIXME: put the record somewhere + # FIXME: should this be an error? + return + new_lines = [] + with open(record_filename) as f: + for line in f: + filename = line.strip() + if os.path.isdir(filename): + filename += os.path.sep + new_lines.append( + os.path.relpath(prepend_root(filename), egg_info_dir) + ) + new_lines.sort() + ensure_dir(egg_info_dir) + inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt') + with open(inst_files_path, 'w') as f: + f.write('\n'.join(new_lines) + '\n') + + def get_install_args( + self, + global_options, # type: Sequence[str] + record_filename, # type: str + root, # type: Optional[str] + prefix, # type: Optional[str] + pycompile # type: bool + ): + # type: (...) -> List[str] + install_args = [sys.executable, "-u"] + install_args.append('-c') + install_args.append(SETUPTOOLS_SHIM % self.setup_py) + install_args += list(global_options) + \ + ['install', '--record', record_filename] + install_args += ['--single-version-externally-managed'] + + if root is not None: + install_args += ['--root', root] + if prefix is not None: + install_args += ['--prefix', prefix] + + if pycompile: + install_args += ["--compile"] + else: + install_args += ["--no-compile"] + + if running_under_virtualenv(): + py_ver_str = 'python' + sysconfig.get_python_version() + install_args += ['--install-headers', + os.path.join(sys.prefix, 'include', 'site', + py_ver_str, self.name)] + + return install_args diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_set.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_set.py new file mode 100644 index 0000000..d1410e9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_set.py @@ -0,0 +1,197 @@ +from __future__ import absolute_import + +import logging +from collections import OrderedDict + +from pip._internal.exceptions import InstallationError +from pip._internal.utils.logging import indent_log +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.wheel import Wheel + +if MYPY_CHECK_RUNNING: + from typing import Optional, List, Tuple, Dict, Iterable # noqa: F401 + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + + +logger = logging.getLogger(__name__) + + +class RequirementSet(object): + + def __init__(self, require_hashes=False, check_supported_wheels=True): + # type: (bool, bool) -> None + """Create a RequirementSet. + """ + + self.requirements = OrderedDict() # type: Dict[str, InstallRequirement] # noqa: E501 + self.require_hashes = require_hashes + self.check_supported_wheels = check_supported_wheels + + # Mapping of alias: real_name + self.requirement_aliases = {} # type: Dict[str, str] + self.unnamed_requirements = [] # type: List[InstallRequirement] + self.successfully_downloaded = [] # type: List[InstallRequirement] + self.reqs_to_cleanup = [] # type: List[InstallRequirement] + + def __str__(self): + reqs = [req for req in self.requirements.values() + if not req.comes_from] + reqs.sort(key=lambda req: req.name.lower()) + return ' '.join([str(req.req) for req in reqs]) + + def __repr__(self): + reqs = [req for req in self.requirements.values()] + reqs.sort(key=lambda req: req.name.lower()) + reqs_str = ', '.join([str(req.req) for req in reqs]) + return ('<%s object; %d requirement(s): %s>' + % (self.__class__.__name__, len(reqs), reqs_str)) + + def add_requirement( + self, + install_req, # type: InstallRequirement + parent_req_name=None, # type: Optional[str] + extras_requested=None # type: Optional[Iterable[str]] + ): + # type: (...) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]] # noqa: E501 + """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. + """ + name = install_req.name + + # 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", + 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) + if self.check_supported_wheels and not wheel.supported(): + raise InstallationError( + "%s is not a supported wheel on this platform." % + wheel.filename + ) + + # This next bit is really a sanity check. + assert install_req.is_direct == (parent_req_name is None), ( + "a direct req shouldn't have a parent and also, " + "a non direct req should have a parent" + ) + + # Unnamed requirements are scanned again and the requirement won't be + # added as a dependency until after scanning. + if not name: + # url or path requirement w/o an egg fragment + self.unnamed_requirements.append(install_req) + return [install_req], None + + try: + existing_req = self.get_requirement(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.specifier != install_req.req.specifier + ) + if has_conflicting_requirement: + raise InstallationError( + "Double requirement given: %s (already in %s, name=%r)" + % (install_req, existing_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.requirements[name] = install_req + # FIXME: what about other normalizations? E.g., _ vs. -? + if name.lower() != name: + self.requirement_aliases[name.lower()] = name + # We'd want to rescan this requirements 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: + self.reqs_to_cleanup.append(install_req) + raise InstallationError( + "Could not satisfy constraints for '%s': " + "installation from path or url cannot be " + "constrained to a version" % name, + ) + # If we're now installing a constraint, mark the existing + # object for real installation. + existing_req.constraint = False + 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, project_name): + # type: (str) -> bool + name = project_name.lower() + if (name in self.requirements and + not self.requirements[name].constraint or + name in self.requirement_aliases and + not self.requirements[self.requirement_aliases[name]].constraint): + return True + return False + + @property + def has_requirements(self): + # type: () -> List[InstallRequirement] + return list(req for req in self.requirements.values() if not + req.constraint) or self.unnamed_requirements + + def get_requirement(self, project_name): + # type: (str) -> InstallRequirement + for name in project_name, project_name.lower(): + if name in self.requirements: + return self.requirements[name] + if name in self.requirement_aliases: + return self.requirements[self.requirement_aliases[name]] + raise KeyError("No project with the name %r" % project_name) + + def cleanup_files(self): + # type: () -> None + """Clean up files, remove builds.""" + logger.debug('Cleaning up...') + with indent_log(): + for req in self.reqs_to_cleanup: + req.remove_temporary_source() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_tracker.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_tracker.py new file mode 100644 index 0000000..82e084a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_tracker.py @@ -0,0 +1,88 @@ +from __future__ import absolute_import + +import contextlib +import errno +import hashlib +import logging +import os + +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Set, Iterator # noqa: F401 + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + from pip._internal.models.link import Link # noqa: F401 + +logger = logging.getLogger(__name__) + + +class RequirementTracker(object): + + def __init__(self): + # type: () -> None + self._root = os.environ.get('PIP_REQ_TRACKER') + if self._root is None: + self._temp_dir = TempDirectory(delete=False, kind='req-tracker') + self._temp_dir.create() + self._root = os.environ['PIP_REQ_TRACKER'] = self._temp_dir.path + logger.debug('Created requirements tracker %r', self._root) + else: + self._temp_dir = None + logger.debug('Re-using requirements tracker %r', self._root) + self._entries = set() # type: Set[InstallRequirement] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.cleanup() + + def _entry_path(self, link): + # type: (Link) -> str + hashed = hashlib.sha224(link.url_without_fragment.encode()).hexdigest() + return os.path.join(self._root, hashed) + + def add(self, req): + # type: (InstallRequirement) -> None + link = req.link + info = str(req) + entry_path = self._entry_path(link) + try: + with open(entry_path) as fp: + # Error, these's already a build in progress. + raise LookupError('%s is already being built: %s' + % (link, fp.read())) + except IOError as e: + if e.errno != errno.ENOENT: + raise + assert req not in self._entries + with open(entry_path, 'w') as fp: + fp.write(info) + self._entries.add(req) + logger.debug('Added %s to build tracker %r', req, self._root) + + def remove(self, req): + # type: (InstallRequirement) -> None + link = req.link + self._entries.remove(req) + os.unlink(self._entry_path(link)) + logger.debug('Removed %s from build tracker %r', req, self._root) + + def cleanup(self): + # type: () -> None + for req in set(self._entries): + self.remove(req) + remove = self._temp_dir is not None + if remove: + self._temp_dir.cleanup() + logger.debug('%s build tracker %r', + 'Removed' if remove else 'Cleaned', + self._root) + + @contextlib.contextmanager + def track(self, req): + # type: (InstallRequirement) -> Iterator[None] + self.add(req) + yield + self.remove(req) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_uninstall.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_uninstall.py new file mode 100644 index 0000000..c80959e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/req/req_uninstall.py @@ -0,0 +1,596 @@ +from __future__ import absolute_import + +import csv +import functools +import logging +import os +import sys +import sysconfig + +from pip._vendor import pkg_resources + +from pip._internal.exceptions import UninstallationError +from pip._internal.locations import bin_py, bin_user +from pip._internal.utils.compat import WINDOWS, cache_from_source, uses_pycache +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local, + normalize_path, renames, rmtree, +) +from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory + +logger = logging.getLogger(__name__) + + +def _script_names(dist, script_name, is_gui): + """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 = bin_user + else: + bin_dir = bin_py + 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 + + +def _unique(fn): + @functools.wraps(fn) + def unique(*args, **kw): + seen = set() + for item in fn(*args, **kw): + if item not in seen: + seen.add(item) + yield item + return unique + + +@_unique +def uninstallation_paths(dist): + """ + Yield all the uninstallation paths for dist based on RECORD-without-.py[co] + + Yield paths to all the files in RECORD. For each .py file in RECORD, add + the .pyc and .pyo in the same directory. + + UninstallPathSet.add() takes care of the __pycache__ .py[co]. + """ + r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD'))) + for row in r: + path = os.path.join(dist.location, row[0]) + yield path + if path.endswith('.py'): + dn, fn = os.path.split(path) + base = fn[:-3] + path = os.path.join(dn, base + '.pyc') + yield path + path = os.path.join(dn, base + '.pyo') + yield path + + +def compact(paths): + """Compact a path set to contain the minimal number of paths + necessary to contain all paths in the set. If /a/path/ and + /a/path/to/a/file.txt are both in the set, leave only the + shorter path.""" + + sep = os.path.sep + short_paths = set() + for path in sorted(paths, key=len): + should_skip = any( + path.startswith(shortpath.rstrip("*")) and + path[len(shortpath.rstrip("*").rstrip(sep))] == sep + for shortpath in short_paths + ) + if not should_skip: + short_paths.add(path) + return short_paths + + +def compress_for_rename(paths): + """Returns a set containing the paths that need to be renamed. + + This set may include directories when the original sequence of paths + included every file on disk. + """ + case_map = dict((os.path.normcase(p), p) for p in paths) + remaining = set(case_map) + unchecked = sorted(set(os.path.split(p)[0] + for p in case_map.values()), key=len) + wildcards = set() + + def norm_join(*a): + return os.path.normcase(os.path.join(*a)) + + for root in unchecked: + if any(os.path.normcase(root).startswith(w) + for w in wildcards): + # This directory has already been handled. + continue + + all_files = set() + all_subdirs = set() + for dirname, subdirs, files in os.walk(root): + all_subdirs.update(norm_join(root, dirname, d) + for d in subdirs) + all_files.update(norm_join(root, dirname, f) + for f in files) + # If all the files we found are in our remaining set of files to + # remove, then remove them from the latter set and add a wildcard + # for the directory. + if not (all_files - remaining): + remaining.difference_update(all_files) + wildcards.add(root + os.sep) + + return set(map(case_map.__getitem__, remaining)) | wildcards + + +def compress_for_output_listing(paths): + """Returns a tuple of 2 sets of which paths to display to user + + The first set contains paths that would be deleted. Files of a package + are not added and the top-level directory of the package has a '*' added + at the end - to signify that all it's contents are removed. + + The second set contains files that would have been skipped in the above + folders. + """ + + will_remove = list(paths) + will_skip = set() + + # Determine folders and files + folders = set() + files = set() + for path in will_remove: + if path.endswith(".pyc"): + continue + if path.endswith("__init__.py") or ".dist-info" in path: + folders.add(os.path.dirname(path)) + files.add(path) + + _normcased_files = set(map(os.path.normcase, files)) + + folders = compact(folders) + + # This walks the tree using os.walk to not miss extra folders + # that might get added. + for folder in folders: + for dirpath, _, dirfiles in os.walk(folder): + for fname in dirfiles: + if fname.endswith(".pyc"): + continue + + file_ = os.path.join(dirpath, fname) + if (os.path.isfile(file_) and + os.path.normcase(file_) not in _normcased_files): + # We are skipping this file. Add it to the set. + will_skip.add(file_) + + will_remove = files | { + os.path.join(folder, "*") for folder in folders + } + + return will_remove, will_skip + + +class StashedUninstallPathSet(object): + """A set of file rename operations to stash files while + tentatively uninstalling them.""" + def __init__(self): + # Mapping from source file root to [Adjacent]TempDirectory + # for files under that directory. + self._save_dirs = {} + # (old path, new path) tuples for each move that may need + # to be undone. + self._moves = [] + + def _get_directory_stash(self, path): + """Stashes a directory. + + Directories are stashed adjacent to their original location if + possible, or else moved/copied into the user's temp dir.""" + + try: + save_dir = AdjacentTempDirectory(path) + save_dir.create() + except OSError: + save_dir = TempDirectory(kind="uninstall") + save_dir.create() + self._save_dirs[os.path.normcase(path)] = save_dir + + return save_dir.path + + def _get_file_stash(self, path): + """Stashes a file. + + If no root has been provided, one will be created for the directory + in the user's temp directory.""" + path = os.path.normcase(path) + head, old_head = os.path.dirname(path), None + save_dir = None + + while head != old_head: + try: + save_dir = self._save_dirs[head] + break + except KeyError: + pass + head, old_head = os.path.dirname(head), head + else: + # Did not find any suitable root + head = os.path.dirname(path) + save_dir = TempDirectory(kind='uninstall') + save_dir.create() + self._save_dirs[head] = save_dir + + relpath = os.path.relpath(path, head) + if relpath and relpath != os.path.curdir: + return os.path.join(save_dir.path, relpath) + return save_dir.path + + def stash(self, path): + """Stashes the directory or file and returns its new location. + """ + if os.path.isdir(path): + new_path = self._get_directory_stash(path) + else: + new_path = self._get_file_stash(path) + + self._moves.append((path, new_path)) + if os.path.isdir(path) and os.path.isdir(new_path): + # If we're moving a directory, we need to + # remove the destination first or else it will be + # moved to inside the existing directory. + # We just created new_path ourselves, so it will + # be removable. + os.rmdir(new_path) + renames(path, new_path) + return new_path + + def commit(self): + """Commits the uninstall by removing stashed files.""" + for _, save_dir in self._save_dirs.items(): + save_dir.cleanup() + self._moves = [] + self._save_dirs = {} + + def rollback(self): + """Undoes the uninstall by moving stashed files back.""" + for p in self._moves: + logging.info("Moving to %s\n from %s", *p) + + for new_path, path in self._moves: + try: + logger.debug('Replacing %s from %s', new_path, path) + if os.path.isfile(new_path): + os.unlink(new_path) + elif os.path.isdir(new_path): + rmtree(new_path) + renames(path, new_path) + except OSError as ex: + logger.error("Failed to restore %s", new_path) + logger.debug("Exception: %s", ex) + + self.commit() + + @property + def can_rollback(self): + return bool(self._moves) + + +class UninstallPathSet(object): + """A set of file paths to be removed in the uninstallation of a + requirement.""" + def __init__(self, dist): + self.paths = set() + self._refuse = set() + self.pth = {} + self.dist = dist + self._moved_paths = StashedUninstallPathSet() + + def _permitted(self, path): + """ + Return True if the given path is one we are permitted to + remove/modify, False otherwise. + + """ + return is_local(path) + + def add(self, path): + head, tail = os.path.split(path) + + # we normalize the head to resolve parent directory symlinks, but not + # the tail, since we only want to uninstall symlinks, not their targets + path = os.path.join(normalize_path(head), os.path.normcase(tail)) + + if not os.path.exists(path): + return + if self._permitted(path): + self.paths.add(path) + else: + self._refuse.add(path) + + # __pycache__ files can show up after 'installed-files.txt' is created, + # due to imports + if os.path.splitext(path)[1] == '.py' and uses_pycache: + self.add(cache_from_source(path)) + + def add_pth(self, pth_file, entry): + 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) + else: + self._refuse.add(pth_file) + + def remove(self, auto_confirm=False, verbose=False): + """Remove paths in ``self.paths`` with confirmation (unless + ``auto_confirm`` is True).""" + + if not self.paths: + logger.info( + "Can't uninstall '%s'. No files were found to uninstall.", + self.dist.project_name, + ) + return + + dist_name_version = ( + self.dist.project_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 path in sorted(compact(for_rename)): + moved.stash(path) + logger.debug('Removing file or directory %s', path) + + for pth in self.pth.values(): + pth.remove() + + logger.info('Successfully uninstalled %s', dist_name_version) + + def _allowed_to_proceed(self, verbose): + """Display which files would be deleted and prompt for confirmation + """ + + def _display(msg, paths): + if not paths: + return + + logger.info(msg) + with indent_log(): + for path in sorted(compact(paths)): + logger.info(path) + + if not verbose: + 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 = list(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)) + + return ask('Proceed (y/n)? ', ('y', 'n')) == 'y' + + def rollback(self): + """Rollback the changes previously made by remove().""" + if not self._moved_paths.can_rollback: + logger.error( + "Can't roll back %s; was not uninstalled", + self.dist.project_name, + ) + return False + logger.info('Rolling back uninstall of %s', self.dist.project_name) + self._moved_paths.rollback() + for pth in self.pth.values(): + pth.rollback() + + def commit(self): + """Remove temporary save dir: rollback will no longer be possible.""" + self._moved_paths.commit() + + @classmethod + def from_dist(cls, dist): + dist_path = normalize_path(dist.location) + if not dist_is_local(dist): + logger.info( + "Not uninstalling %s at %s, outside environment %s", + dist.key, + dist_path, + sys.prefix, + ) + return cls(dist) + + if dist_path 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, + ) + return cls(dist) + + paths_to_remove = cls(dist) + develop_egg_link = egg_link_path(dist) + develop_egg_link_egg_info = '{}.egg-info'.format( + pkg_resources.to_filename(dist.project_name)) + 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) + # 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: + namespaces = [] + for top_level_pkg in [ + p for p + in dist.get_metadata('top_level.txt').splitlines() + if p and p not in namespaces]: + 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') + + elif distutils_egg_info: + 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, + ) + ) + + elif dist.location.endswith('.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] + easy_install_pth = os.path.join(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'): + for path in uninstallation_paths(dist): + paths_to_remove.add(path) + + elif develop_egg_link: + # develop egg + with open(develop_egg_link, 'r') as fh: + link_pointer = os.path.normcase(fh.readline().strip()) + assert (link_pointer == dist.location), ( + 'Egg-link %s does not match installed location of %s ' + '(at %s)' % (link_pointer, dist.project_name, 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) + + else: + logger.debug( + 'Not sure how to uninstall: %s - Check: %s', + dist, dist.location, + ) + + # 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 = bin_user + else: + bin_dir = bin_py + 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(s) + + return paths_to_remove + + +class UninstallPthEntries(object): + def __init__(self, pth_file): + if not os.path.isfile(pth_file): + raise UninstallationError( + "Cannot remove entries from nonexistent file %s" % pth_file + ) + self.file = pth_file + self.entries = set() + self._saved_lines = None + + def add(self, entry): + entry = os.path.normcase(entry) + # On Windows, os.path.normcase converts the entry to use + # backslashes. This is correct for entries that describe absolute + # paths outside of site-packages, but all the others use forward + # slashes. + if WINDOWS and not os.path.splitdrive(entry)[0]: + entry = entry.replace('\\', '/') + self.entries.add(entry) + + def remove(self): + logger.debug('Removing pth entries from %s:', self.file) + with open(self.file, 'rb') as fh: + # windows uses '\r\n' with py3k, but uses '\n' with py2.x + lines = fh.readlines() + self._saved_lines = lines + if any(b'\r\n' in line for line in lines): + endline = '\r\n' + else: + endline = '\n' + # handle missing trailing newline + if lines and not lines[-1].endswith(endline.encode("utf-8")): + lines[-1] = lines[-1] + endline.encode("utf-8") + for entry in self.entries: + try: + logger.debug('Removing entry: %s', entry) + lines.remove((entry + endline).encode("utf-8")) + except ValueError: + pass + with open(self.file, 'wb') as fh: + fh.writelines(lines) + + def rollback(self): + if self._saved_lines is None: + logger.error( + 'Cannot roll back changes to %s, none were made', self.file + ) + return False + logger.debug('Rolling %s back to previous state', self.file) + with open(self.file, 'wb') as fh: + fh.writelines(self._saved_lines) + return True diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/resolve.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/resolve.py new file mode 100644 index 0000000..33f572f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/resolve.py @@ -0,0 +1,393 @@ +"""Dependency Resolution + +The dependency resolution in pip is performed as follows: + +for top-level requirements: + a. only one spec allowed per project, regardless of conflicts or not. + otherwise a "double requirement" exception is raised + b. they override sub-dependency requirements. +for sub-dependencies + a. "first found, wins" (where the order is breadth first) +""" + +import logging +from collections import defaultdict +from itertools import chain + +from pip._internal.exceptions import ( + BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors, + UnsupportedPythonVersion, +) +from pip._internal.req.constructors import install_req_from_req_string +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import dist_in_usersite, ensure_dir +from pip._internal.utils.packaging import check_dist_requires_python +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional, DefaultDict, List, Set # noqa: F401 + from pip._internal.download import PipSession # noqa: F401 + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + from pip._internal.index import PackageFinder # noqa: F401 + from pip._internal.req.req_set import RequirementSet # noqa: F401 + from pip._internal.operations.prepare import ( # noqa: F401 + DistAbstraction, RequirementPreparer + ) + from pip._internal.cache import WheelCache # noqa: F401 + +logger = logging.getLogger(__name__) + + +class Resolver(object): + """Resolves which packages need to be installed/uninstalled to perform \ + the requested operation without breaking the requirements of any package. + """ + + _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} + + def __init__( + self, + preparer, # type: RequirementPreparer + session, # type: PipSession + finder, # type: PackageFinder + wheel_cache, # type: Optional[WheelCache] + use_user_site, # type: bool + ignore_dependencies, # type: bool + ignore_installed, # type: bool + ignore_requires_python, # type: bool + force_reinstall, # type: bool + isolated, # type: bool + upgrade_strategy, # type: str + use_pep517=None # type: Optional[bool] + ): + # type: (...) -> None + super(Resolver, self).__init__() + assert upgrade_strategy in self._allowed_strategies + + self.preparer = preparer + self.finder = finder + self.session = session + + # NOTE: This would eventually be replaced with a cache that can give + # information about both sdist and wheels transparently. + self.wheel_cache = wheel_cache + + # This is set in resolve + self.require_hashes = None # type: Optional[bool] + + self.upgrade_strategy = upgrade_strategy + self.force_reinstall = force_reinstall + self.isolated = isolated + self.ignore_dependencies = ignore_dependencies + self.ignore_installed = ignore_installed + self.ignore_requires_python = ignore_requires_python + self.use_user_site = use_user_site + self.use_pep517 = use_pep517 + + self._discovered_dependencies = \ + defaultdict(list) # type: DefaultDict[str, List] + + def resolve(self, requirement_set): + # type: (RequirementSet) -> None + """Resolve what operations need to be done + + As a side-effect of this method, the packages (and their dependencies) + are downloaded, unpacked and prepared for installation. This + preparation is done by ``pip.operations.prepare``. + + Once PyPI has static dependency metadata available, it would be + possible to move the preparation to become a step separated from + dependency resolution. + """ + # make the wheelhouse + if self.preparer.wheel_download_dir: + ensure_dir(self.preparer.wheel_download_dir) + + # If any top-level requirement has a hash specified, enter + # hash-checking mode, which requires hashes from all. + root_reqs = ( + requirement_set.unnamed_requirements + + list(requirement_set.requirements.values()) + ) + self.require_hashes = ( + requirement_set.require_hashes or + any(req.has_hash_options for req in root_reqs) + ) + + # Display where finder is looking for packages + locations = self.finder.get_formatted_locations() + if locations: + logger.info(locations) + + # Actually prepare the files, and collect any exceptions. Most hash + # exceptions cannot be checked ahead of time, because + # req.populate_link() needs to be called before we can make decisions + # based on link type. + discovered_reqs = [] # type: List[InstallRequirement] + hash_errors = HashErrors() + for req in chain(root_reqs, discovered_reqs): + try: + discovered_reqs.extend( + self._resolve_one(requirement_set, req) + ) + except HashError as exc: + exc.req = req + hash_errors.append(exc) + + if hash_errors: + raise hash_errors + + def _is_upgrade_allowed(self, req): + # type: (InstallRequirement) -> bool + if self.upgrade_strategy == "to-satisfy-only": + return False + elif self.upgrade_strategy == "eager": + return True + else: + assert self.upgrade_strategy == "only-if-needed" + return req.is_direct + + def _set_req_to_reinstall(self, req): + # type: (InstallRequirement) -> None + """ + Set a requirement to be installed. + """ + # 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): + req.conflicts_with = req.satisfied_by + req.satisfied_by = None + + # XXX: Stop passing requirement_set for options + def _check_skip_installed(self, req_to_install): + # type: (InstallRequirement) -> Optional[str] + """Check if req_to_install should be skipped. + + This will check if the req is installed, and whether we should upgrade + or reinstall it, taking into account all the relevant user options. + + After calling this req_to_install will only have satisfied_by set to + None if the req_to_install is to be upgraded/reinstalled etc. Any + other value will be a dist recording the current thing installed that + satisfies the requirement. + + Note that for vcs urls and the like we can't assess skipping in this + routine - we simply identify that we need to pull the thing down, + then later on it is pulled down and introspected to assess upgrade/ + reinstalls etc. + + :return: A text reason for why it was skipped, or None. + """ + if self.ignore_installed: + return None + + req_to_install.check_if_exists(self.use_user_site) + if not req_to_install.satisfied_by: + return None + + if self.force_reinstall: + self._set_req_to_reinstall(req_to_install) + return None + + if not self._is_upgrade_allowed(req_to_install): + if self.upgrade_strategy == "only-if-needed": + return 'already satisfied, skipping upgrade' + return 'already satisfied' + + # Check for the possibility of an upgrade. For link-based + # requirements we have to pull the tree down and inspect to assess + # the version #, so it's handled way down. + if not req_to_install.link: + try: + self.finder.find_requirement(req_to_install, upgrade=True) + except BestVersionAlreadyInstalled: + # Then the best version is installed. + return 'already up-to-date' + except DistributionNotFound: + # No distribution found, so we squash the error. It will + # be raised later when we re-try later to do the install. + # Why don't we just raise here? + pass + + self._set_req_to_reinstall(req_to_install) + return None + + def _get_abstract_dist_for(self, req): + # type: (InstallRequirement) -> DistAbstraction + """Takes a InstallRequirement and returns a single AbstractDist \ + representing a prepared variant of the same. + """ + assert self.require_hashes is not None, ( + "require_hashes should have been set in Resolver.resolve()" + ) + + if req.editable: + return self.preparer.prepare_editable_requirement( + req, self.require_hashes, self.use_user_site, self.finder, + ) + + # satisfied_by is only evaluated by calling _check_skip_installed, + # so it must be None here. + assert req.satisfied_by is None + skip_reason = self._check_skip_installed(req) + + if req.satisfied_by: + return self.preparer.prepare_installed_requirement( + req, self.require_hashes, skip_reason + ) + + upgrade_allowed = self._is_upgrade_allowed(req) + abstract_dist = self.preparer.prepare_linked_requirement( + req, self.session, self.finder, upgrade_allowed, + self.require_hashes + ) + + # NOTE + # The following portion is for determining if a certain package is + # going to be re-installed/upgraded or not and reporting to the user. + # This should probably get cleaned up in a future refactor. + + # req.req is only avail after unpack for URL + # pkgs repeat check_if_exists to uninstall-on-upgrade + # (#14) + if not self.ignore_installed: + req.check_if_exists(self.use_user_site) + + if req.satisfied_by: + should_modify = ( + self.upgrade_strategy != "to-satisfy-only" or + self.force_reinstall or + self.ignore_installed or + req.link.scheme == 'file' + ) + if should_modify: + self._set_req_to_reinstall(req) + else: + logger.info( + 'Requirement already satisfied (use --upgrade to upgrade):' + ' %s', req, + ) + + return abstract_dist + + def _resolve_one( + self, + requirement_set, # type: RequirementSet + req_to_install # type: InstallRequirement + ): + # type: (...) -> List[InstallRequirement] + """Prepare a single requirements file. + + :return: A list of additional InstallRequirements to also install. + """ + # Tell user what we are doing for this requirement: + # obtain (editable), skipping, processing (local url), collecting + # (remote url or package name) + if req_to_install.constraint or req_to_install.prepared: + return [] + + req_to_install.prepared = True + + # register tmp src for cleanup in case something goes wrong + requirement_set.reqs_to_cleanup.append(req_to_install) + + abstract_dist = self._get_abstract_dist_for(req_to_install) + + # Parse and return dependencies + dist = abstract_dist.dist() + try: + check_dist_requires_python(dist) + except UnsupportedPythonVersion as err: + if self.ignore_requires_python: + logger.warning(err.args[0]) + else: + raise + + more_reqs = [] # type: List[InstallRequirement] + + def add_req(subreq, extras_requested): + sub_install_req = install_req_from_req_string( + str(subreq), + req_to_install, + isolated=self.isolated, + wheel_cache=self.wheel_cache, + use_pep517=self.use_pep517 + ) + parent_req_name = req_to_install.name + to_scan_again, add_to_parent = requirement_set.add_requirement( + sub_install_req, + parent_req_name=parent_req_name, + extras_requested=extras_requested, + ) + if parent_req_name and add_to_parent: + self._discovered_dependencies[parent_req_name].append( + add_to_parent + ) + more_reqs.extend(to_scan_again) + + with indent_log(): + # We add req_to_install before its dependencies, so that we + # can refer to it when adding dependencies. + if not requirement_set.has_requirement(req_to_install.name): + # 'unnamed' requirements will get added here + req_to_install.is_direct = True + requirement_set.add_requirement( + req_to_install, parent_req_name=None, + ) + + if not self.ignore_dependencies: + if req_to_install.extras: + logger.debug( + "Installing extra requirements: %r", + ','.join(req_to_install.extras), + ) + missing_requested = sorted( + set(req_to_install.extras) - set(dist.extras) + ) + for missing in missing_requested: + logger.warning( + '%s does not provide the extra \'%s\'', + dist, missing + ) + + available_requested = sorted( + set(dist.extras) & set(req_to_install.extras) + ) + for subreq in dist.requires(available_requested): + add_req(subreq, extras_requested=available_requested) + + if not req_to_install.editable and not req_to_install.satisfied_by: + # XXX: --no-install leads this to report 'Successfully + # downloaded' for only non-editable reqs, even though we took + # action on them. + requirement_set.successfully_downloaded.append(req_to_install) + + return more_reqs + + def get_installation_order(self, req_set): + # type: (RequirementSet) -> List[InstallRequirement] + """Create the installation order. + + The installation order is topological - requirements are installed + before the requiring thing. We break cycles at an arbitrary point, + and make no other guarantees. + """ + # The current implementation, which we may change at any point + # installs the user specified things in the order given, except when + # dependencies must come earlier to achieve topological order. + order = [] + ordered_reqs = set() # type: Set[InstallRequirement] + + def schedule(req): + if req.satisfied_by or req in ordered_reqs: + return + if req.constraint: + return + ordered_reqs.add(req) + for dep in self._discovered_dependencies[req.name]: + schedule(dep) + order.append(req) + + for install_req in req_set.requirements.values(): + schedule(install_req) + return order diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/appdirs.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/appdirs.py new file mode 100644 index 0000000..9af9fa7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/appdirs.py @@ -0,0 +1,270 @@ +""" +This code was taken from https://github.com/ActiveState/appdirs and modified +to suit our purposes. +""" +from __future__ import absolute_import + +import os +import sys + +from pip._vendor.six import PY2, text_type + +from pip._internal.utils.compat import WINDOWS, expanduser +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + List, Union + ) + + +def user_cache_dir(appname): + # type: (str) -> str + r""" + Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + + Typical user cache directories are: + macOS: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Windows: C:\Users\\AppData\Local\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go + in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the + non-roaming app data dir (the default returned by `user_data_dir`). Apps + typically put cache data somewhere *under* the given dir here. Some + examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + """ + if WINDOWS: + # Get the base path + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + + # When using Python 2, return paths as bytes on Windows like we do on + # other operating systems. See helper function docs for more details. + if PY2 and isinstance(path, text_type): + path = _win_path_to_bytes(path) + + # Add our app name and Cache directory to it + path = os.path.join(path, appname, "Cache") + elif sys.platform == "darwin": + # Get the base path + path = expanduser("~/Library/Caches") + + # Add our app name to it + path = os.path.join(path, appname) + else: + # Get the base path + path = os.getenv("XDG_CACHE_HOME", expanduser("~/.cache")) + + # Add our app name to it + path = os.path.join(path, appname) + + return path + + +def user_data_dir(appname, roaming=False): + # type: (str, bool) -> str + r""" + Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + macOS: ~/Library/Application Support/ + if it exists, else ~/.config/ + Unix: ~/.local/share/ # or in + $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\ ... + ...Application Data\ + Win XP (roaming): C:\Documents and Settings\\Local ... + ...Settings\Application Data\ + Win 7 (not roaming): C:\\Users\\AppData\Local\ + Win 7 (roaming): C:\\Users\\AppData\Roaming\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if WINDOWS: + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.join(os.path.normpath(_get_win_folder(const)), appname) + elif sys.platform == "darwin": + path = os.path.join( + expanduser('~/Library/Application Support/'), + appname, + ) if os.path.isdir(os.path.join( + expanduser('~/Library/Application Support/'), + appname, + ) + ) else os.path.join( + expanduser('~/.config/'), + appname, + ) + else: + path = os.path.join( + os.getenv('XDG_DATA_HOME', expanduser("~/.local/share")), + appname, + ) + + return path + + +def user_config_dir(appname, roaming=True): + # type: (str, bool) -> str + """Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "roaming" (boolean, default True) can be set False to not use the + Windows roaming appdata directory. That means that for users on a + Windows network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + macOS: same as user_data_dir + Unix: ~/.config/ + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if WINDOWS: + path = user_data_dir(appname, roaming=roaming) + elif sys.platform == "darwin": + path = user_data_dir(appname) + else: + path = os.getenv('XDG_CONFIG_HOME', expanduser("~/.config")) + path = os.path.join(path, appname) + + return path + + +# for the discussion regarding site_config_dirs locations +# see +def site_config_dirs(appname): + # type: (str) -> List[str] + r"""Return a list of potential user-shared config dirs for this application. + + "appname" is the name of application. + + Typical user config directories are: + macOS: /Library/Application Support// + Unix: /etc or $XDG_CONFIG_DIRS[i]// for each value in + $XDG_CONFIG_DIRS + Win XP: C:\Documents and Settings\All Users\Application ... + ...Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory + on Vista.) + Win 7: Hidden, but writeable on Win 7: + C:\ProgramData\\ + """ + if WINDOWS: + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + pathlist = [os.path.join(path, appname)] + elif sys.platform == 'darwin': + pathlist = [os.path.join('/Library/Application Support', appname)] + else: + # try looking in $XDG_CONFIG_DIRS + xdg_config_dirs = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + if xdg_config_dirs: + pathlist = [ + os.path.join(expanduser(x), appname) + for x in xdg_config_dirs.split(os.pathsep) + ] + else: + pathlist = [] + + # always look in /etc directly as well + pathlist.append('/etc') + + return pathlist + + +# -- Windows support functions -- + +def _get_win_folder_from_registry(csidl_name): + # type: (str) -> str + """ + This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + directory, _type = _winreg.QueryValueEx(key, shell_folder_name) + return directory + + +def _get_win_folder_with_ctypes(csidl_name): + # type: (str) -> str + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + + +if WINDOWS: + try: + import ctypes + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +def _win_path_to_bytes(path): + """Encode Windows paths to bytes. Only used on Python 2. + + Motivation is to be consistent with other operating systems where paths + are also returned as bytes. This avoids problems mixing bytes and Unicode + elsewhere in the codebase. For more details and discussion see + . + + If encoding using ASCII and MBCS fails, return the original Unicode path. + """ + for encoding in ('ASCII', 'MBCS'): + try: + return path.encode(encoding) + except (UnicodeEncodeError, LookupError): + pass + return path diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/compat.py new file mode 100644 index 0000000..2d8b3bf --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/compat.py @@ -0,0 +1,264 @@ +"""Stuff that differs in different Python versions and platform +distributions.""" +from __future__ import absolute_import, division + +import codecs +import locale +import logging +import os +import shutil +import sys + +from pip._vendor.six import text_type + +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Tuple, Text # noqa: F401 + +try: + import ipaddress +except ImportError: + try: + from pip._vendor import ipaddress # type: ignore + except ImportError: + import ipaddr as ipaddress # type: ignore + ipaddress.ip_address = ipaddress.IPAddress # type: ignore + ipaddress.ip_network = ipaddress.IPNetwork # type: ignore + + +__all__ = [ + "ipaddress", "uses_pycache", "console_to_str", "native_str", + "get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size", + "get_extension_suffixes", +] + + +logger = logging.getLogger(__name__) + +if sys.version_info >= (3, 4): + uses_pycache = True + from importlib.util import cache_from_source +else: + import imp + + try: + cache_from_source = imp.cache_from_source # type: ignore + except AttributeError: + # does not use __pycache__ + cache_from_source = None + + uses_pycache = cache_from_source is not None + + +if sys.version_info >= (3, 5): + backslashreplace_decode = "backslashreplace" +else: + # In version 3.4 and older, backslashreplace exists + # but does not support use for decoding. + # We implement our own replace handler for this + # situation, so that we can consistently use + # backslash replacement for all versions. + def backslashreplace_decode_fn(err): + raw_bytes = (err.object[i] for i in range(err.start, err.end)) + if sys.version_info[0] == 2: + # Python 2 gave us characters - convert to numeric bytes + raw_bytes = (ord(b) for b in raw_bytes) + return u"".join(u"\\x%x" % c for c in raw_bytes), err.end + codecs.register_error( + "backslashreplace_decode", + backslashreplace_decode_fn, + ) + backslashreplace_decode = "backslashreplace_decode" + + +def console_to_str(data): + # type: (bytes) -> Text + """Return a string, safe for output, of subprocess output. + + We assume the data is in the locale preferred encoding. + If it won't decode properly, we warn the user but decode as + best we can. + + We also ensure that the output can be safely written to + standard output without encoding errors. + """ + + # First, get the encoding we assume. This is the preferred + # encoding for the locale, unless that is not found, or + # it is ASCII, in which case assume UTF-8 + encoding = locale.getpreferredencoding() + if (not encoding) or codecs.lookup(encoding).name == "ascii": + encoding = "utf-8" + + # Now try to decode the data - if we fail, warn the user and + # decode with replacement. + try: + decoded_data = data.decode(encoding) + except UnicodeDecodeError: + logger.warning( + "Subprocess output does not appear to be encoded as %s", + encoding, + ) + decoded_data = data.decode(encoding, errors=backslashreplace_decode) + + # Make sure we can print the output, by encoding it to the output + # encoding with replacement of unencodable characters, and then + # decoding again. + # We use stderr's encoding because it's less likely to be + # redirected and if we don't find an encoding we skip this + # step (on the assumption that output is wrapped by something + # that won't fail). + # The double getattr is to deal with the possibility that we're + # being called in a situation where sys.__stderr__ doesn't exist, + # or doesn't have an encoding attribute. Neither of these cases + # should occur in normal pip use, but there's no harm in checking + # in case people use pip in (unsupported) unusual situations. + output_encoding = getattr(getattr(sys, "__stderr__", None), + "encoding", None) + + if output_encoding: + output_encoded = decoded_data.encode( + output_encoding, + errors="backslashreplace" + ) + decoded_data = output_encoded.decode(output_encoding) + + return decoded_data + + +if sys.version_info >= (3,): + def native_str(s, replace=False): + # type: (str, bool) -> str + if isinstance(s, bytes): + return s.decode('utf-8', 'replace' if replace else 'strict') + return s + +else: + def native_str(s, replace=False): + # type: (str, bool) -> str + # Replace is ignored -- unicode to UTF-8 can't fail + if isinstance(s, text_type): + return s.encode('utf-8') + return s + + +def get_path_uid(path): + # type: (str) -> int + """ + Return path's uid. + + Does not follow symlinks: + https://github.com/pypa/pip/pull/935#discussion_r5307003 + + Placed this function in compat due to differences on AIX and + Jython, that should eventually go away. + + :raises OSError: When path is a symlink or can't be read. + """ + if hasattr(os, 'O_NOFOLLOW'): + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW) + file_uid = os.fstat(fd).st_uid + os.close(fd) + else: # AIX and Jython + # WARNING: time of check vulnerability, but best we can do w/o NOFOLLOW + if not os.path.islink(path): + # older versions of Jython don't have `os.fstat` + file_uid = os.stat(path).st_uid + else: + # raise OSError for parity with os.O_NOFOLLOW above + raise OSError( + "%s is a symlink; Will not return uid for symlinks" % path + ) + return file_uid + + +if sys.version_info >= (3, 4): + from importlib.machinery import EXTENSION_SUFFIXES + + def get_extension_suffixes(): + return EXTENSION_SUFFIXES +else: + from imp import get_suffixes + + def get_extension_suffixes(): + return [suffix[0] for suffix in get_suffixes()] + + +def expanduser(path): + # type: (str) -> str + """ + Expand ~ and ~user constructions. + + Includes a workaround for https://bugs.python.org/issue14768 + """ + expanded = os.path.expanduser(path) + if path.startswith('~/') and expanded.startswith('//'): + expanded = expanded[1:] + return expanded + + +# packages in the stdlib that may have installation metadata, but should not be +# considered 'installed'. this theoretically could be determined based on +# dist.location (py27:`sysconfig.get_paths()['stdlib']`, +# py26:sysconfig.get_config_vars('LIBDEST')), but fear platform variation may +# make this ineffective, so hard-coding +stdlib_pkgs = {"python", "wsgiref", "argparse"} + + +# windows detection, covers cpython and ironpython +WINDOWS = (sys.platform.startswith("win") or + (sys.platform == 'cli' and os.name == 'nt')) + + +def samefile(file1, file2): + # type: (str, str) -> bool + """Provide an alternative for os.path.samefile on Windows/Python2""" + if hasattr(os.path, 'samefile'): + return os.path.samefile(file1, file2) + else: + path1 = os.path.normcase(os.path.abspath(file1)) + path2 = os.path.normcase(os.path.abspath(file2)) + return path1 == path2 + + +if hasattr(shutil, 'get_terminal_size'): + def get_terminal_size(): + # type: () -> Tuple[int, int] + """ + Returns a tuple (x, y) representing the width(x) and the height(y) + in characters of the terminal window. + """ + return tuple(shutil.get_terminal_size()) # type: ignore +else: + def get_terminal_size(): + # type: () -> Tuple[int, int] + """ + Returns a tuple (x, y) representing the width(x) and the height(y) + in characters of the terminal window. + """ + def ioctl_GWINSZ(fd): + try: + import fcntl + import termios + import struct + cr = struct.unpack_from( + 'hh', + fcntl.ioctl(fd, termios.TIOCGWINSZ, '12345678') + ) + except Exception: + return None + if cr == (0, 0): + return None + return cr + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except Exception: + pass + if not cr: + cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80)) + return int(cr[1]), int(cr[0]) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/deprecation.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/deprecation.py new file mode 100644 index 0000000..0beaf74 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/deprecation.py @@ -0,0 +1,90 @@ +""" +A module that implements tooling to enable easy warnings about deprecations. +""" +from __future__ import absolute_import + +import logging +import warnings + +from pip._vendor.packaging.version import parse + +from pip import __version__ as current_version +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Any, Optional # noqa: F401 + + +class PipDeprecationWarning(Warning): + pass + + +_original_showwarning = None # type: Any + + +# Warnings <-> Logging Integration +def _showwarning(message, category, filename, lineno, file=None, line=None): + if file is not None: + if _original_showwarning is not None: + _original_showwarning( + message, category, filename, lineno, file, line, + ) + elif issubclass(category, PipDeprecationWarning): + # We use a specially named logger which will handle all of the + # deprecation messages for pip. + logger = logging.getLogger("pip._internal.deprecations") + logger.warning(message) + else: + _original_showwarning( + message, category, filename, lineno, file, line, + ) + + +def install_warning_logger(): + # type: () -> None + # Enable our Deprecation Warnings + warnings.simplefilter("default", PipDeprecationWarning, append=True) + + global _original_showwarning + + if _original_showwarning is None: + _original_showwarning = warnings.showwarning + warnings.showwarning = _showwarning + + +def deprecated(reason, replacement, gone_in, issue=None): + # type: (str, Optional[str], Optional[str], Optional[int]) -> None + """Helper to deprecate existing functionality. + + reason: + Textual reason shown to the user about why this functionality has + been deprecated. + replacement: + Textual suggestion shown to the user about what alternative + functionality they can use. + gone_in: + The version of pip does this functionality should get removed in. + Raises errors if pip's current version is greater than or equal to + this. + issue: + Issue number on the tracker that would serve as a useful place for + users to find related discussion and provide feedback. + + Always pass replacement, gone_in and issue as keyword arguments for clarity + at the call site. + """ + + # Construct a nice message. + # This is purposely eagerly formatted as we want it to appear as if someone + # typed this entire message out. + message = "DEPRECATION: " + reason + if replacement is not None: + message += " A possible replacement is {}.".format(replacement) + if issue is not None: + url = "https://github.com/pypa/pip/issues/" + str(issue) + message += " You can find discussion regarding this at {}.".format(url) + + # Raise as an error if it has to be removed. + if gone_in is not None and parse(current_version) >= parse(gone_in): + raise PipDeprecationWarning(message) + warnings.warn(message, category=PipDeprecationWarning, stacklevel=2) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/encoding.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/encoding.py new file mode 100644 index 0000000..d36defa --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/encoding.py @@ -0,0 +1,39 @@ +import codecs +import locale +import re +import sys + +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import List, Tuple, Text # noqa: F401 + +BOMS = [ + (codecs.BOM_UTF8, 'utf8'), + (codecs.BOM_UTF16, 'utf16'), + (codecs.BOM_UTF16_BE, 'utf16-be'), + (codecs.BOM_UTF16_LE, 'utf16-le'), + (codecs.BOM_UTF32, 'utf32'), + (codecs.BOM_UTF32_BE, 'utf32-be'), + (codecs.BOM_UTF32_LE, 'utf32-le'), +] # type: List[Tuple[bytes, Text]] + +ENCODING_RE = re.compile(br'coding[:=]\s*([-\w.]+)') + + +def auto_decode(data): + # type: (bytes) -> Text + """Check a bytes string for a BOM to correctly detect the encoding + + Fallback to locale.getpreferredencoding(False) like open() on Python3""" + for bom, encoding in BOMS: + if data.startswith(bom): + return data[len(bom):].decode(encoding) + # Lets check the first two lines as in PEP263 + for line in data.split(b'\n')[:2]: + if line[0:1] == b'#' and ENCODING_RE.search(line): + encoding = ENCODING_RE.search(line).groups()[0].decode('ascii') + return data.decode(encoding) + return data.decode( + locale.getpreferredencoding(False) or sys.getdefaultencoding(), + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/filesystem.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/filesystem.py new file mode 100644 index 0000000..1e6b033 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/filesystem.py @@ -0,0 +1,30 @@ +import os +import os.path + +from pip._internal.utils.compat import get_path_uid + + +def check_path_owner(path): + # type: (str) -> bool + # If we don't have a way to check the effective uid of this process, then + # we'll just assume that we own the directory. + if not hasattr(os, "geteuid"): + return True + + previous = None + while path != previous: + if os.path.lexists(path): + # Check if path is writable by current user. + if os.geteuid() == 0: + # Special handling for root user in order to handle properly + # cases where users use sudo without -H flag. + try: + path_uid = get_path_uid(path) + except OSError: + return False + return path_uid == 0 + else: + return os.access(path, os.W_OK) + else: + previous, path = path, os.path.dirname(path) + return False # assume we don't own the path diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/glibc.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/glibc.py new file mode 100644 index 0000000..8a51f69 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/glibc.py @@ -0,0 +1,93 @@ +from __future__ import absolute_import + +import ctypes +import re +import warnings + +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional, Tuple # noqa: F401 + + +def glibc_version_string(): + # type: () -> Optional[str] + "Returns glibc version string, or None if not using glibc." + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + process_namespace = ctypes.CDLL(None) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing +def check_glibc_version(version_str, required_major, minimum_minor): + # type: (str, int, int) -> bool + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn("Expected glibc version with 2 components major.minor," + " got: %s" % version_str, RuntimeWarning) + return False + return (int(m.group("major")) == required_major and + int(m.group("minor")) >= minimum_minor) + + +def have_compatible_glibc(required_major, minimum_minor): + # type: (int, int) -> bool + version_str = glibc_version_string() # type: Optional[str] + if version_str is None: + return False + return check_glibc_version(version_str, required_major, minimum_minor) + + +# platform.libc_ver regularly returns completely nonsensical glibc +# versions. E.g. on my computer, platform says: +# +# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.7') +# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.9') +# +# But the truth is: +# +# ~$ ldd --version +# ldd (Debian GLIBC 2.22-11) 2.22 +# +# This is unfortunate, because it means that the linehaul data on libc +# versions that was generated by pip 8.1.2 and earlier is useless and +# misleading. Solution: instead of using platform, use our code that actually +# works. +def libc_ver(): + # type: () -> Tuple[str, str] + """Try to determine the glibc version + + Returns a tuple of strings (lib, version) which default to empty strings + in case the lookup fails. + """ + glibc_version = glibc_version_string() + if glibc_version is None: + return ("", "") + else: + return ("glibc", glibc_version) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/hashes.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/hashes.py new file mode 100644 index 0000000..c6df7a1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/hashes.py @@ -0,0 +1,115 @@ +from __future__ import absolute_import + +import hashlib + +from pip._vendor.six import iteritems, iterkeys, itervalues + +from pip._internal.exceptions import ( + HashMismatch, HashMissing, InstallationError, +) +from pip._internal.utils.misc import read_chunks +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Dict, List, BinaryIO, NoReturn, Iterator + ) + from pip._vendor.six import PY3 + if PY3: + from hashlib import _Hash # noqa: F401 + else: + from hashlib import _hash as _Hash # noqa: F401 + + +# The recommended hash algo of the moment. Change this whenever the state of +# the art changes; it won't hurt backward compatibility. +FAVORITE_HASH = 'sha256' + + +# Names of hashlib algorithms allowed by the --hash option and ``pip hash`` +# Currently, those are the ones at least as collision-resistant as sha256. +STRONG_HASHES = ['sha256', 'sha384', 'sha512'] + + +class Hashes(object): + """A wrapper that builds multiple hashes at once and checks them against + known-good values + + """ + def __init__(self, hashes=None): + # type: (Dict[str, List[str]]) -> None + """ + :param hashes: A dict of algorithm names pointing to lists of allowed + hex digests + """ + self._allowed = {} if hashes is None else hashes + + def check_against_chunks(self, chunks): + # type: (Iterator[bytes]) -> None + """Check good hashes against ones built from iterable of chunks of + data. + + Raise HashMismatch if none match. + + """ + gots = {} + for hash_name in iterkeys(self._allowed): + try: + gots[hash_name] = hashlib.new(hash_name) + except (ValueError, TypeError): + raise InstallationError('Unknown hash name: %s' % hash_name) + + for chunk in chunks: + for hash in itervalues(gots): + hash.update(chunk) + + for hash_name, got in iteritems(gots): + if got.hexdigest() in self._allowed[hash_name]: + return + self._raise(gots) + + def _raise(self, gots): + # type: (Dict[str, _Hash]) -> NoReturn + raise HashMismatch(self._allowed, gots) + + def check_against_file(self, file): + # type: (BinaryIO) -> None + """Check good hashes against a file-like object + + Raise HashMismatch if none match. + + """ + return self.check_against_chunks(read_chunks(file)) + + def check_against_path(self, path): + # type: (str) -> None + with open(path, 'rb') as file: + return self.check_against_file(file) + + def __nonzero__(self): + # type: () -> bool + """Return whether I know any known-good hashes.""" + return bool(self._allowed) + + def __bool__(self): + # type: () -> bool + return self.__nonzero__() + + +class MissingHashes(Hashes): + """A workalike for Hashes used when we're missing a hash for a requirement + + It computes the actual hash of the requirement and raises a HashMissing + exception showing it to the user. + + """ + def __init__(self): + # type: () -> None + """Don't offer the ``hashes`` kwarg.""" + # Pass our favorite hash in to generate a "gotten hash". With the + # empty list, it will never match, so an error will always raise. + super(MissingHashes, self).__init__(hashes={FAVORITE_HASH: []}) + + def _raise(self, gots): + # type: (Dict[str, _Hash]) -> NoReturn + raise HashMissing(gots[FAVORITE_HASH].hexdigest()) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/logging.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/logging.py new file mode 100644 index 0000000..579d696 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/logging.py @@ -0,0 +1,318 @@ +from __future__ import absolute_import + +import contextlib +import errno +import logging +import logging.handlers +import os +import sys + +from pip._vendor.six import PY2 + +from pip._internal.utils.compat import WINDOWS +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() +_log_state.indentation = 0 + + +class BrokenStdoutLoggingError(Exception): + """ + Raised if BrokenPipeError occurs for the stdout stream while logging. + """ + pass + + +# BrokenPipeError does not exist in Python 2 and, in addition, manifests +# differently in Windows and non-Windows. +if WINDOWS: + # In Windows, a broken pipe can show up as EINVAL rather than EPIPE: + # https://bugs.python.org/issue19612 + # https://bugs.python.org/issue30418 + if PY2: + def _is_broken_pipe_error(exc_class, exc): + """See the docstring for non-Windows Python 3 below.""" + return (exc_class is IOError and + exc.errno in (errno.EINVAL, errno.EPIPE)) + else: + # In Windows, a broken pipe IOError became OSError in Python 3. + def _is_broken_pipe_error(exc_class, exc): + """See the docstring for non-Windows Python 3 below.""" + return ((exc_class is BrokenPipeError) or # noqa: F821 + (exc_class is OSError and + exc.errno in (errno.EINVAL, errno.EPIPE))) +elif PY2: + def _is_broken_pipe_error(exc_class, exc): + """See the docstring for non-Windows Python 3 below.""" + return (exc_class is IOError and exc.errno == errno.EPIPE) +else: + # Then we are in the non-Windows Python 3 case. + def _is_broken_pipe_error(exc_class, exc): + """ + Return whether an exception is a broken pipe error. + + Args: + exc_class: an exception class. + exc: an exception instance. + """ + return (exc_class is BrokenPipeError) # noqa: F821 + + +@contextlib.contextmanager +def indent_log(num=2): + """ + A context manager which will cause the log output to be indented for any + log messages emitted inside it. + """ + _log_state.indentation += num + try: + yield + finally: + _log_state.indentation -= num + + +def get_indentation(): + return getattr(_log_state, 'indentation', 0) + + +class IndentingFormatter(logging.Formatter): + def __init__(self, *args, **kwargs): + """ + A logging.Formatter obeying containing indent_log contexts. + + :param add_timestamp: A bool indicating output lines should be prefixed + with their record's timestamp. + """ + self.add_timestamp = kwargs.pop("add_timestamp", False) + super(IndentingFormatter, self).__init__(*args, **kwargs) + + def format(self, record): + """ + Calls the standard formatter, but will indent all of the log messages + by our current indentation level. + """ + formatted = super(IndentingFormatter, self).format(record) + prefix = '' + if self.add_timestamp: + prefix = self.formatTime(record, "%Y-%m-%dT%H:%M:%S ") + prefix += " " * get_indentation() + formatted = "".join([ + prefix + line + for line in formatted.splitlines(True) + ]) + return formatted + + +def _color_wrap(*colors): + def wrapped(inp): + 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=None, no_color=None): + logging.StreamHandler.__init__(self, stream) + self._no_color = no_color + + if WINDOWS and colorama: + self.stream = colorama.AnsiToWin32(self.stream) + + def _using_stdout(self): + """ + Return whether the handler is using sys.stdout. + """ + if WINDOWS and colorama: + # Then self.stream is an AnsiToWin32 object. + return self.stream.wrapped is sys.stdout + + return self.stream is sys.stdout + + def should_color(self): + # 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 + ) + + # 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): + msg = logging.StreamHandler.format(self, record) + + if self.should_color(): + for level, color in self.COLORS: + if record.levelno >= level: + msg = color(msg) + break + + return msg + + # The logging module says handleError() can be customized. + def handleError(self, record): + 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 + # exception so we can handle it in main() instead of logging the + # broken pipe error and continuing. + if (exc_class and self._using_stdout() and + _is_broken_pipe_error(exc_class, exc)): + raise BrokenStdoutLoggingError() + + return super(ColorizedStreamHandler, self).handleError(record) + + +class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler): + + def _open(self): + ensure_dir(os.path.dirname(self.baseFilename)) + return logging.handlers.RotatingFileHandler._open(self) + + +class MaxLevelFilter(logging.Filter): + + def __init__(self, level): + self.level = level + + def filter(self, record): + return record.levelno < self.level + + +def setup_logging(verbosity, no_color, user_log_file): + """Configures and sets up all of the logging + + Returns the requested logging level, as its integer value. + """ + + # Determine the level to be logging at. + if verbosity >= 1: + level = "DEBUG" + elif verbosity == -1: + level = "WARNING" + elif verbosity == -2: + level = "ERROR" + elif verbosity <= -3: + level = "CRITICAL" + else: + level = "INFO" + + level_number = getattr(logging, level) + + # The "root" logger should match the "console" level *unless* we also need + # to log to a user log file. + include_user_log = user_log_file is not None + if include_user_log: + additional_log_file = user_log_file + root_level = "DEBUG" + else: + additional_log_file = "/dev/null" + root_level = level + + # Disable any logging besides WARNING unless we have DEBUG level logging + # enabled for vendored libraries. + vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG" + + # Shorthands for clarity + log_streams = { + "stdout": "ext://sys.stdout", + "stderr": "ext://sys.stderr", + } + handler_classes = { + "stream": "pip._internal.utils.logging.ColorizedStreamHandler", + "file": "pip._internal.utils.logging.BetterRotatingFileHandler", + } + + logging.config.dictConfig({ + "version": 1, + "disable_existing_loggers": False, + "filters": { + "exclude_warnings": { + "()": "pip._internal.utils.logging.MaxLevelFilter", + "level": logging.WARNING, + }, + }, + "formatters": { + "indent": { + "()": IndentingFormatter, + "format": "%(message)s", + }, + "indent_with_timestamp": { + "()": IndentingFormatter, + "format": "%(message)s", + "add_timestamp": True, + }, + }, + "handlers": { + "console": { + "level": level, + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stdout"], + "filters": ["exclude_warnings"], + "formatter": "indent", + }, + "console_errors": { + "level": "WARNING", + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stderr"], + "formatter": "indent", + }, + "user_log": { + "level": "DEBUG", + "class": handler_classes["file"], + "filename": additional_log_file, + "delay": True, + "formatter": "indent_with_timestamp", + }, + }, + "root": { + "level": root_level, + "handlers": ["console", "console_errors"] + ( + ["user_log"] if include_user_log else [] + ), + }, + "loggers": { + "pip._vendor": { + "level": vendored_log_level + } + }, + }) + + return level_number diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/misc.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/misc.py new file mode 100644 index 0000000..84605ee --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/misc.py @@ -0,0 +1,1040 @@ +from __future__ import absolute_import + +import contextlib +import errno +import io +import locale +# we have a submodule named 'logging' which would shadow this if we used the +# regular name: +import logging as std_logging +import os +import posixpath +import re +import shutil +import stat +import subprocess +import sys +import tarfile +import zipfile +from collections import deque + +from pip._vendor import pkg_resources +# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is +# why we ignore the type on this import. +from pip._vendor.retrying import retry # type: ignore +from pip._vendor.six import PY2 +from pip._vendor.six.moves import input +from pip._vendor.six.moves.urllib import parse as urllib_parse +from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote + +from pip._internal.exceptions import CommandError, InstallationError +from pip._internal.locations import ( + running_under_virtualenv, site_packages, user_site, virtualenv_no_global, + write_delete_marker_file, +) +from pip._internal.utils.compat import ( + WINDOWS, console_to_str, expanduser, stdlib_pkgs, +) +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if PY2: + from io import BytesIO as StringIO +else: + from io import StringIO + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Optional, Tuple, Iterable, List, Match, Union, Any, Mapping, Text, + AnyStr, Container + ) + from pip._vendor.pkg_resources import Distribution # noqa: F401 + from pip._internal.models.link import Link # noqa: F401 + from pip._internal.utils.ui import SpinnerInterface # noqa: F401 + + +__all__ = ['rmtree', 'display_path', 'backup_dir', + 'ask', 'splitext', + 'format_size', 'is_installable_dir', + 'is_svn_page', 'file_contents', + 'split_leading_dir', 'has_leading_dir', + 'normalize_path', + 'renames', 'get_prog', + 'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess', + 'captured_stdout', 'ensure_dir', + 'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS', 'WHEEL_EXTENSION', + 'get_installed_version', 'remove_auth_from_url'] + + +logger = std_logging.getLogger(__name__) + +WHEEL_EXTENSION = '.whl' +BZ2_EXTENSIONS = ('.tar.bz2', '.tbz') +XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma') +ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION) +TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar') +ARCHIVE_EXTENSIONS = ( + ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS) +SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS + +try: + import bz2 # noqa + SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS +except ImportError: + logger.debug('bz2 module is not available') + +try: + # Only for Python 3.3+ + import lzma # noqa + SUPPORTED_EXTENSIONS += XZ_EXTENSIONS +except ImportError: + logger.debug('lzma module is not available') + + +def ensure_dir(path): + # type: (AnyStr) -> None + """os.path.makedirs without EEXIST.""" + try: + os.makedirs(path) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + +def get_prog(): + # type: () -> str + try: + prog = os.path.basename(sys.argv[0]) + if prog in ('__main__.py', '-c'): + return "%s -m pip" % sys.executable + else: + return prog + except (AttributeError, TypeError, IndexError): + pass + return 'pip' + + +# Retry every half second for up to 3 seconds +@retry(stop_max_delay=3000, wait_fixed=500) +def rmtree(dir, ignore_errors=False): + # type: (str, bool) -> None + shutil.rmtree(dir, ignore_errors=ignore_errors, + onerror=rmtree_errorhandler) + + +def rmtree_errorhandler(func, path, exc_info): + """On Windows, the files in .svn are read-only, so when rmtree() tries to + remove them, an exception is thrown. We catch that here, remove the + read-only attribute, and hopefully continue without problems.""" + # if file type currently read only + if os.stat(path).st_mode & stat.S_IREAD: + # convert to read/write + os.chmod(path, stat.S_IWRITE) + # use the original function to repeat the operation + func(path) + return + else: + raise + + +def display_path(path): + # type: (Union[str, Text]) -> str + """Gives the display value for a given path, making it relative to cwd + if possible.""" + path = os.path.normcase(os.path.abspath(path)) + if sys.version_info[0] == 2: + path = path.decode(sys.getfilesystemencoding(), 'replace') + path = path.encode(sys.getdefaultencoding(), 'replace') + if path.startswith(os.getcwd() + os.path.sep): + path = '.' + path[len(os.getcwd()):] + return path + + +def backup_dir(dir, ext='.bak'): + # type: (str, str) -> str + """Figure out the name of a directory to back up the given dir to + (adding .bak, .bak2, etc)""" + n = 1 + extension = ext + while os.path.exists(dir + extension): + n += 1 + extension = ext + str(n) + return dir + extension + + +def ask_path_exists(message, options): + # type: (str, Iterable[str]) -> str + for action in os.environ.get('PIP_EXISTS_ACTION', '').split(): + if action in options: + return action + return ask(message, options) + + +def ask(message, options): + # type: (str, Iterable[str]) -> str + """Ask the message interactively, with the given possible responses""" + while 1: + if os.environ.get('PIP_NO_INPUT'): + raise Exception( + 'No input was expected ($PIP_NO_INPUT set); question: %s' % + message + ) + response = input(message) + response = response.strip().lower() + if response not in options: + print( + 'Your response (%r) was not one of the expected responses: ' + '%s' % (response, ', '.join(options)) + ) + else: + return response + + +def format_size(bytes): + # type: (float) -> str + if bytes > 1000 * 1000: + return '%.1fMB' % (bytes / 1000.0 / 1000) + elif bytes > 10 * 1000: + return '%ikB' % (bytes / 1000) + elif bytes > 1000: + return '%.1fkB' % (bytes / 1000.0) + else: + return '%ibytes' % bytes + + +def is_installable_dir(path): + # type: (str) -> bool + """Is path is a directory containing setup.py or pyproject.toml? + """ + if not os.path.isdir(path): + return False + setup_py = os.path.join(path, 'setup.py') + if os.path.isfile(setup_py): + return True + pyproject_toml = os.path.join(path, 'pyproject.toml') + if os.path.isfile(pyproject_toml): + return True + return False + + +def is_svn_page(html): + # type: (Union[str, Text]) -> Optional[Match[Union[str, Text]]] + """ + Returns true if the page appears to be the index page of an svn repository + """ + return (re.search(r'[^<]*Revision \d+:', html) and + re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I)) + + +def file_contents(filename): + # type: (str) -> Text + with open(filename, 'rb') as fp: + return fp.read().decode('utf-8') + + +def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): + """Yield pieces of data from a file-like object until EOF.""" + while True: + chunk = file.read(size) + if not chunk: + break + yield chunk + + +def split_leading_dir(path): + # type: (Union[str, Text]) -> List[Union[str, Text]] + path = path.lstrip('/').lstrip('\\') + if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or + '\\' not in path): + return path.split('/', 1) + elif '\\' in path: + return path.split('\\', 1) + else: + return [path, ''] + + +def has_leading_dir(paths): + # type: (Iterable[Union[str, Text]]) -> bool + """Returns true if all the paths have the same leading path name + (i.e., everything is in one subdirectory in an archive)""" + common_prefix = None + for path in paths: + prefix, rest = split_leading_dir(path) + if not prefix: + return False + elif common_prefix is None: + common_prefix = prefix + elif prefix != common_prefix: + return False + return True + + +def normalize_path(path, resolve_symlinks=True): + # type: (str, bool) -> str + """ + Convert a path to its canonical, case-normalized, absolute version. + + """ + path = expanduser(path) + if resolve_symlinks: + path = os.path.realpath(path) + else: + path = os.path.abspath(path) + return os.path.normcase(path) + + +def splitext(path): + # type: (str) -> Tuple[str, str] + """Like os.path.splitext, but take off .tar too""" + base, ext = posixpath.splitext(path) + if base.lower().endswith('.tar'): + ext = base[-4:] + ext + base = base[:-4] + return base, ext + + +def renames(old, new): + # type: (str, str) -> None + """Like os.renames(), but handles renaming across devices.""" + # Implementation borrowed from os.renames(). + head, tail = os.path.split(new) + if head and tail and not os.path.exists(head): + os.makedirs(head) + + shutil.move(old, new) + + head, tail = os.path.split(old) + if head and tail: + try: + os.removedirs(head) + except OSError: + pass + + +def is_local(path): + # type: (str) -> bool + """ + Return True if path is within sys.prefix, if we're running in a virtualenv. + + If we're not in a virtualenv, all paths are considered "local." + + """ + if not running_under_virtualenv(): + return True + return normalize_path(path).startswith(normalize_path(sys.prefix)) + + +def dist_is_local(dist): + # type: (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): + # type: (Distribution) -> bool + """ + Return True if given Distribution is installed in user site. + """ + norm_path = normalize_path(dist_location(dist)) + return norm_path.startswith(normalize_path(user_site)) + + +def dist_in_site_packages(dist): + # type: (Distribution) -> bool + """ + Return True if given Distribution is installed in + sysconfig.get_python_lib(). + """ + return normalize_path( + dist_location(dist) + ).startswith(normalize_path(site_packages)) + + +def dist_is_editable(dist): + # type: (Distribution) -> bool + """ + Return True if given Distribution is an editable install. + """ + for path_item in sys.path: + egg_link = os.path.join(path_item, dist.project_name + '.egg-link') + if os.path.isfile(egg_link): + return True + return False + + +def get_installed_distributions(local_only=True, + skip=stdlib_pkgs, + include_editables=True, + editables_only=False, + user_only=False): + # type: (bool, Container[str], bool, bool, bool) -> List[Distribution] + """ + Return a list of installed Distribution objects. + + If ``local_only`` is True (default), only return installations + local to the current virtualenv, if in a virtualenv. + + ``skip`` argument is an iterable of lower-case project names to + ignore; defaults to stdlib_pkgs + + If ``include_editables`` is False, don't report editables. + + If ``editables_only`` is True , only report editables. + + If ``user_only`` is True , only report installations in the user + site directory. + + """ + if local_only: + local_test = dist_is_local + else: + def local_test(d): + return True + + if include_editables: + def editable_test(d): + return True + else: + def editable_test(d): + return not dist_is_editable(d) + + if editables_only: + def editables_only_test(d): + return dist_is_editable(d) + else: + def editables_only_test(d): + return True + + if user_only: + user_test = dist_in_usersite + else: + def user_test(d): + return True + + # because of pkg_resources vendoring, mypy cannot find stub in typeshed + return [d for d in pkg_resources.working_set # type: ignore + if local_test(d) and + d.key not in skip and + editable_test(d) and + editables_only_test(d) and + user_test(d) + ] + + +def egg_link_path(dist): + # type: (Distribution) -> Optional[str] + """ + Return the path for the .egg-link file if it exists, otherwise, None. + + There's 3 scenarios: + 1) not in a virtualenv + try to find in site.USER_SITE, then site_packages + 2) in a no-global virtualenv + try to find in site_packages + 3) in a yes-global virtualenv + try to find in site_packages, then site.USER_SITE + (don't look in global location) + + For #1 and #3, there could be odd cases, where there's an egg-link in 2 + locations. + + This method will just return the first one found. + """ + sites = [] + if running_under_virtualenv(): + if virtualenv_no_global(): + sites.append(site_packages) + else: + sites.append(site_packages) + if user_site: + sites.append(user_site) + else: + if user_site: + sites.append(user_site) + sites.append(site_packages) + + for site in sites: + egglink = os.path.join(site, dist.project_name) + '.egg-link' + if os.path.isfile(egglink): + return egglink + return None + + +def dist_location(dist): + # type: (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. + + """ + egg_link = egg_link_path(dist) + if egg_link: + return egg_link + return dist.location + + +def current_umask(): + """Get the current umask which involves having to set it temporarily.""" + mask = os.umask(0) + os.umask(mask) + return mask + + +def unzip_file(filename, location, flatten=True): + # type: (str, str, bool) -> None + """ + Unzip the file (with path `filename`) to the destination `location`. All + files are written based on system defaults and umask (i.e. permissions are + not preserved), except that regular file members with any execute + permissions (user, group, or world) have "chmod +x" applied after being + written. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) + zipfp = open(filename, 'rb') + try: + zip = zipfile.ZipFile(zipfp, allowZip64=True) + leading = has_leading_dir(zip.namelist()) and flatten + for info in zip.infolist(): + name = info.filename + fn = name + if leading: + fn = split_leading_dir(name)[1] + fn = os.path.join(location, fn) + dir = os.path.dirname(fn) + if fn.endswith('/') or fn.endswith('\\'): + # A directory + ensure_dir(fn) + else: + ensure_dir(dir) + # Don't use read() to avoid allocating an arbitrarily large + # chunk of memory for the file's content + fp = zip.open(name) + try: + with open(fn, 'wb') as destfp: + shutil.copyfileobj(fp, destfp) + finally: + fp.close() + mode = info.external_attr >> 16 + # if mode and regular file and any execute permissions for + # user/group/world? + if mode and stat.S_ISREG(mode) and mode & 0o111: + # make dest file have execute for user/group/world + # (chmod +x) no-op on windows per python docs + os.chmod(fn, (0o777 - current_umask() | 0o111)) + finally: + zipfp.close() + + +def untar_file(filename, location): + # type: (str, str) -> None + """ + Untar the file (with path `filename`) to the destination `location`. + All files are written based on system defaults and umask (i.e. permissions + are not preserved), except that regular file members with any execute + permissions (user, group, or world) have "chmod +x" applied after being + written. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) + if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'): + mode = 'r:gz' + elif filename.lower().endswith(BZ2_EXTENSIONS): + mode = 'r:bz2' + elif filename.lower().endswith(XZ_EXTENSIONS): + mode = 'r:xz' + elif filename.lower().endswith('.tar'): + mode = 'r' + else: + logger.warning( + 'Cannot determine compression type for file %s', filename, + ) + mode = 'r:*' + tar = tarfile.open(filename, mode) + try: + leading = has_leading_dir([ + member.name for member in tar.getmembers() + ]) + for member in tar.getmembers(): + fn = member.name + if leading: + # https://github.com/python/mypy/issues/1174 + fn = split_leading_dir(fn)[1] # type: ignore + path = os.path.join(location, fn) + if member.isdir(): + ensure_dir(path) + elif member.issym(): + try: + # https://github.com/python/typeshed/issues/2673 + tar._extract_member(member, path) # type: ignore + except Exception as exc: + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + logger.warning( + 'In the tar file %s the member %s is invalid: %s', + filename, member.name, exc, + ) + continue + else: + try: + fp = tar.extractfile(member) + except (KeyError, AttributeError) as exc: + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + logger.warning( + 'In the tar file %s the member %s is invalid: %s', + filename, member.name, exc, + ) + continue + ensure_dir(os.path.dirname(path)) + with open(path, 'wb') as destfp: + shutil.copyfileobj(fp, destfp) + fp.close() + # Update the timestamp (useful for cython compiled files) + # https://github.com/python/typeshed/issues/2673 + tar.utime(member, path) # type: ignore + # member have any execute permissions for user/group/world? + if member.mode & 0o111: + # make dest file have execute for user/group/world + # no-op on windows per python docs + os.chmod(path, (0o777 - current_umask() | 0o111)) + finally: + tar.close() + + +def unpack_file( + filename, # type: str + location, # type: str + content_type, # type: Optional[str] + link # type: Optional[Link] +): + # type: (...) -> None + filename = os.path.realpath(filename) + if (content_type == 'application/zip' or + filename.lower().endswith(ZIP_EXTENSIONS) or + zipfile.is_zipfile(filename)): + unzip_file( + filename, + location, + flatten=not filename.endswith('.whl') + ) + elif (content_type == 'application/x-gzip' or + tarfile.is_tarfile(filename) or + filename.lower().endswith( + TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)): + untar_file(filename, location) + elif (content_type and content_type.startswith('text/html') and + is_svn_page(file_contents(filename))): + # We don't really care about this + from pip._internal.vcs.subversion import Subversion + Subversion('svn+' + link.url).unpack(location) + else: + # FIXME: handle? + # FIXME: magic signatures? + logger.critical( + 'Cannot unpack file %s (downloaded from %s, content-type: %s); ' + 'cannot detect archive format', + filename, location, content_type, + ) + raise InstallationError( + 'Cannot determine archive format of %s' % location + ) + + +def call_subprocess( + cmd, # type: List[str] + show_stdout=True, # type: bool + cwd=None, # type: Optional[str] + on_returncode='raise', # type: str + extra_ok_returncodes=None, # type: Optional[Iterable[int]] + command_desc=None, # type: Optional[str] + extra_environ=None, # type: Optional[Mapping[str, Any]] + unset_environ=None, # type: Optional[Iterable[str]] + spinner=None # type: Optional[SpinnerInterface] +): + # type: (...) -> Optional[Text] + """ + Args: + extra_ok_returncodes: an iterable of integer return codes that are + acceptable, in addition to 0. Defaults to None, which means []. + unset_environ: an iterable of environment variable names to unset + prior to calling subprocess.Popen(). + """ + if extra_ok_returncodes is None: + extra_ok_returncodes = [] + if unset_environ is None: + unset_environ = [] + # This function's handling of subprocess output is confusing and I + # previously broke it terribly, so as penance I will write a long comment + # explaining things. + # + # The obvious thing that affects output is the show_stdout= + # kwarg. show_stdout=True means, let the subprocess write directly to our + # stdout. Even though it is nominally the default, it is almost never used + # inside pip (and should not be used in new code without a very good + # reason); as of 2016-02-22 it is only used in a few places inside the VCS + # wrapper code. Ideally we should get rid of it entirely, because it + # creates a lot of complexity here for a rarely used feature. + # + # Most places in pip set show_stdout=False. What this means is: + # - We connect the child stdout to a pipe, which we read. + # - By default, we hide the output but show a spinner -- unless the + # subprocess exits with an error, in which case we show the output. + # - If the --verbose option was passed (= loglevel is DEBUG), then we show + # the output unconditionally. (But in this case we don't want to show + # the output a second time if it turns out that there was an error.) + # + # stderr is always merged with stdout (even if show_stdout=True). + if show_stdout: + stdout = None + else: + stdout = subprocess.PIPE + if command_desc is None: + cmd_parts = [] + for part in cmd: + if ' ' in part or '\n' in part or '"' in part or "'" in part: + part = '"%s"' % part.replace('"', '\\"') + cmd_parts.append(part) + command_desc = ' '.join(cmd_parts) + logger.debug("Running command %s", command_desc) + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + for name in unset_environ: + env.pop(name, None) + try: + proc = subprocess.Popen( + cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, + stdout=stdout, cwd=cwd, env=env, + ) + proc.stdin.close() + except Exception as exc: + logger.critical( + "Error %s while executing command %s", exc, command_desc, + ) + raise + all_output = [] + if stdout is not None: + while True: + line = console_to_str(proc.stdout.readline()) + if not line: + break + line = line.rstrip() + all_output.append(line + '\n') + if logger.getEffectiveLevel() <= std_logging.DEBUG: + # Show the line immediately + logger.debug(line) + else: + # Update the spinner + if spinner is not None: + spinner.spin() + try: + proc.wait() + finally: + if proc.stdout: + proc.stdout.close() + if spinner is not None: + if proc.returncode: + spinner.finish("error") + else: + spinner.finish("done") + if proc.returncode and proc.returncode not in extra_ok_returncodes: + if on_returncode == 'raise': + if (logger.getEffectiveLevel() > std_logging.DEBUG and + not show_stdout): + logger.info( + 'Complete output from command %s:', command_desc, + ) + logger.info( + ''.join(all_output) + + '\n----------------------------------------' + ) + raise InstallationError( + 'Command "%s" failed with error code %s in %s' + % (command_desc, proc.returncode, cwd)) + elif on_returncode == 'warn': + logger.warning( + 'Command "%s" had error code %s in %s', + command_desc, proc.returncode, cwd, + ) + elif on_returncode == 'ignore': + pass + else: + raise ValueError('Invalid value: on_returncode=%s' % + repr(on_returncode)) + if not show_stdout: + return ''.join(all_output) + return None + + +def read_text_file(filename): + # type: (str) -> str + """Return the contents of *filename*. + + Try to decode the file contents with utf-8, the preferred system encoding + (e.g., cp1252 on some Windows machines), and latin1, in that order. + Decoding a byte string with latin1 will never raise an error. In the worst + case, the returned string will contain some garbage characters. + + """ + with open(filename, 'rb') as fp: + data = fp.read() + + encodings = ['utf-8', locale.getpreferredencoding(False), 'latin1'] + for enc in encodings: + try: + # https://github.com/python/mypy/issues/1174 + data = data.decode(enc) # type: ignore + except UnicodeDecodeError: + continue + break + + assert not isinstance(data, bytes) # Latin1 should have worked. + return data + + +def _make_build_dir(build_dir): + os.makedirs(build_dir) + write_delete_marker_file(build_dir) + + +class FakeFile(object): + """Wrap a list of lines in an object with readline() to make + ConfigParser happy.""" + def __init__(self, lines): + self._gen = (l for l in lines) + + def readline(self): + try: + try: + return next(self._gen) + except NameError: + return self._gen.next() + except StopIteration: + return '' + + def __iter__(self): + return self._gen + + +class StreamWrapper(StringIO): + + @classmethod + def from_stream(cls, orig_stream): + cls.orig_stream = orig_stream + return cls() + + # compileall.compile_dir() needs stdout.encoding to print to stdout + @property + def encoding(self): + return self.orig_stream.encoding + + +@contextlib.contextmanager +def captured_output(stream_name): + """Return a context manager used by captured_stdout/stdin/stderr + that temporarily replaces the sys stream *stream_name* with a StringIO. + + Taken from Lib/support/__init__.py in the CPython repo. + """ + orig_stdout = getattr(sys, stream_name) + setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout)) + try: + yield getattr(sys, stream_name) + finally: + setattr(sys, stream_name, orig_stdout) + + +def captured_stdout(): + """Capture the output of sys.stdout: + + with captured_stdout() as stdout: + print('hello') + self.assertEqual(stdout.getvalue(), 'hello\n') + + Taken from Lib/support/__init__.py in the CPython repo. + """ + return captured_output('stdout') + + +def captured_stderr(): + """ + See captured_stdout(). + """ + return captured_output('stderr') + + +class cached_property(object): + """A property that is only computed once per instance and then replaces + itself with an ordinary attribute. Deleting the attribute resets the + property. + + Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175 + """ + + def __init__(self, func): + self.__doc__ = getattr(func, '__doc__') + self.func = func + + def __get__(self, obj, cls): + if obj is None: + # We're being accessed from the class itself, not from an object + return self + value = obj.__dict__[self.func.__name__] = self.func(obj) + return value + + +def get_installed_version(dist_name, working_set=None): + """Get the installed version of dist_name avoiding pkg_resources cache""" + # Create a requirement that we'll look for inside of setuptools. + req = pkg_resources.Requirement.parse(dist_name) + + if working_set is None: + # We want to avoid having this cached, so we need to construct a new + # working set each time. + working_set = pkg_resources.WorkingSet() + + # Get the installed distribution from our working set + dist = working_set.find(req) + + # Check to see if we got an installed distribution or not, if we did + # we want to return it's version. + return dist.version if dist else None + + +def consume(iterator): + """Consume an iterable at C speed.""" + deque(iterator, maxlen=0) + + +# Simulates an enum +def enum(*sequential, **named): + enums = dict(zip(sequential, range(len(sequential))), **named) + reverse = {value: key for key, value in enums.items()} + enums['reverse_mapping'] = reverse + return type('Enum', (), enums) + + +def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None): + """ + Return the URL for a VCS requirement. + + Args: + repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+"). + project_name: the (unescaped) project name. + """ + egg_project_name = pkg_resources.to_filename(project_name) + req = '{}@{}#egg={}'.format(repo_url, rev, egg_project_name) + if subdir: + req += '&subdirectory={}'.format(subdir) + + return req + + +def split_auth_from_netloc(netloc): + """ + Parse out and remove the auth information from a netloc. + + Returns: (netloc, (username, password)). + """ + if '@' not in netloc: + return netloc, (None, None) + + # Split from the right because that's how urllib.parse.urlsplit() + # behaves if more than one @ is present (which can be checked using + # the password attribute of urlsplit()'s return value). + auth, netloc = netloc.rsplit('@', 1) + if ':' in auth: + # Split from the left because that's how urllib.parse.urlsplit() + # behaves if more than one : is present (which again can be checked + # using the password attribute of the return value) + user_pass = auth.split(':', 1) + else: + user_pass = auth, None + + user_pass = tuple( + None if x is None else urllib_unquote(x) for x in user_pass + ) + + return netloc, user_pass + + +def redact_netloc(netloc): + # type: (str) -> str + """ + Replace the password in a netloc with "****", if it exists. + + For example, "user:pass@example.com" returns "user:****@example.com". + """ + netloc, (user, password) = split_auth_from_netloc(netloc) + if user is None: + return netloc + password = '' if password is None else ':****' + return '{user}{password}@{netloc}'.format(user=urllib_parse.quote(user), + password=password, + netloc=netloc) + + +def _transform_url(url, transform_netloc): + purl = urllib_parse.urlsplit(url) + netloc = transform_netloc(purl.netloc) + # stripped url + url_pieces = ( + purl.scheme, netloc, purl.path, purl.query, purl.fragment + ) + surl = urllib_parse.urlunsplit(url_pieces) + return surl + + +def _get_netloc(netloc): + return split_auth_from_netloc(netloc)[0] + + +def remove_auth_from_url(url): + # type: (str) -> str + # Return a copy of url with 'username:password@' removed. + # username/pass params are passed to subversion through flags + # and are not recognized in the url. + return _transform_url(url, _get_netloc) + + +def redact_password_from_url(url): + # type: (str) -> str + """Replace the password in a given url with ****.""" + return _transform_url(url, redact_netloc) + + +def protect_pip_from_modification_on_windows(modifying_pip): + """Protection of pip.exe from modification on Windows + + On Windows, any operation modifying pip should be run as: + python -m pip ... + """ + pip_names = [ + "pip.exe", + "pip{}.exe".format(sys.version_info[0]), + "pip{}.{}.exe".format(*sys.version_info[:2]) + ] + + # See https://github.com/pypa/pip/issues/1299 for more discussion + should_show_use_python_msg = ( + modifying_pip and + WINDOWS and + os.path.basename(sys.argv[0]) in pip_names + ) + + if should_show_use_python_msg: + new_command = [ + sys.executable, "-m", "pip" + ] + sys.argv[1:] + raise CommandError( + 'To modify pip, please run the following command:\n{}' + .format(" ".join(new_command)) + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/models.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/models.py new file mode 100644 index 0000000..d5cb80a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/models.py @@ -0,0 +1,40 @@ +"""Utilities for defining models +""" + +import operator + + +class KeyBasedCompareMixin(object): + """Provides comparision capabilities that is based on a key + """ + + def __init__(self, key, defining_class): + self._compare_key = key + self._defining_class = defining_class + + def __hash__(self): + return hash(self._compare_key) + + def __lt__(self, other): + return self._compare(other, operator.__lt__) + + def __le__(self, other): + return self._compare(other, operator.__le__) + + def __gt__(self, other): + return self._compare(other, operator.__gt__) + + def __ge__(self, other): + return self._compare(other, operator.__ge__) + + def __eq__(self, other): + return self._compare(other, operator.__eq__) + + def __ne__(self, other): + return self._compare(other, operator.__ne__) + + def _compare(self, other, method): + if not isinstance(other, self._defining_class): + return NotImplemented + + return method(self._compare_key, other._compare_key) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/outdated.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/outdated.py new file mode 100644 index 0000000..37c47a4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/outdated.py @@ -0,0 +1,164 @@ +from __future__ import absolute_import + +import datetime +import json +import logging +import os.path +import sys + +from pip._vendor import lockfile, pkg_resources +from pip._vendor.packaging import version as packaging_version + +from pip._internal.index import PackageFinder +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.filesystem import check_path_owner +from pip._internal.utils.misc import ensure_dir, get_installed_version +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + import optparse # noqa: F401 + from typing import Any, Dict # noqa: F401 + from pip._internal.download import PipSession # noqa: F401 + + +SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ" + + +logger = logging.getLogger(__name__) + + +class SelfCheckState(object): + def __init__(self, cache_dir): + # type: (str) -> None + self.state = {} # type: Dict[str, Any] + self.statefile_path = None + + # Try to load the existing state + if cache_dir: + self.statefile_path = os.path.join(cache_dir, "selfcheck.json") + try: + with open(self.statefile_path) as statefile: + self.state = json.load(statefile)[sys.prefix] + except (IOError, ValueError, KeyError): + # Explicitly suppressing exceptions, since we don't want to + # error out if the cache file is invalid. + pass + + def save(self, pypi_version, current_time): + # type: (str, datetime.datetime) -> None + # If we do not have a path to cache in, don't bother saving. + 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)): + 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)) + + # Attempt to write out our version check file + with lockfile.LockFile(self.statefile_path): + if os.path.exists(self.statefile_path): + with open(self.statefile_path) as statefile: + state = json.load(statefile) + else: + state = {} + + state[sys.prefix] = { + "last_check": current_time.strftime(SELFCHECK_DATE_FMT), + "pypi_version": pypi_version, + } + + with open(self.statefile_path, "w") as statefile: + json.dump(state, statefile, sort_keys=True, + separators=(",", ":")) + + +def was_installed_by_pip(pkg): + # type: (str) -> bool + """Checks whether pkg was installed by pip + + This is used not to display the upgrade message when pip is in fact + installed by system package manager, such as dnf on Fedora. + """ + try: + dist = pkg_resources.get_distribution(pkg) + return (dist.has_metadata('INSTALLER') and + 'pip' in dist.get_metadata_lines('INSTALLER')) + except pkg_resources.DistributionNotFound: + return False + + +def pip_version_check(session, options): + # type: (PipSession, optparse.Values) -> None + """Check for an update for pip. + + Limit the frequency of checks to once per week. State is stored either in + the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix + of the pip script path. + """ + installed_version = get_installed_version("pip") + if not installed_version: + return + + pip_version = packaging_version.parse(installed_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 + finder = PackageFinder( + find_links=options.find_links, + index_urls=[options.index_url] + options.extra_index_urls, + allow_all_prereleases=False, # Explicitly set to False + trusted_hosts=options.trusted_hosts, + session=session, + ) + all_candidates = finder.find_all_candidates("pip") + if not all_candidates: + return + pypi_version = str( + max(all_candidates, key=lambda c: c.version).version + ) + + # save that we've performed a check + state.save(pypi_version, current_time) + + remote_version = packaging_version.parse(pypi_version) + + # Determine if our pypi_version is older + if (pip_version < remote_version and + pip_version.base_version != remote_version.base_version and + was_installed_by_pip('pip')): + # Advise "python -m pip" on Windows to avoid issues + # with overwriting pip.exe. + if WINDOWS: + pip_cmd = "python -m pip" + else: + pip_cmd = "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 + ) + except Exception: + logger.debug( + "There was an error checking the latest version of pip", + exc_info=True, + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/packaging.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/packaging.py new file mode 100644 index 0000000..7aaf7b5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/packaging.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import + +import logging +import sys +from email.parser import FeedParser + +from pip._vendor import pkg_resources +from pip._vendor.packaging import specifiers, version + +from pip._internal import exceptions +from pip._internal.utils.misc import display_path +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional # noqa: F401 + from email.message import Message # noqa: F401 + from pip._vendor.pkg_resources import Distribution # noqa: F401 + + +logger = logging.getLogger(__name__) + + +def check_requires_python(requires_python): + # type: (Optional[str]) -> bool + """ + Check if the python version in use match the `requires_python` specifier. + + Returns `True` if the version of python in use matches the requirement. + Returns `False` if the version of python in use does not matches the + requirement. + + Raises an InvalidSpecifier if `requires_python` have an invalid format. + """ + if requires_python is None: + # The package provides no information + return True + requires_python_specifier = specifiers.SpecifierSet(requires_python) + + # We only use major.minor.micro + python_version = version.parse('.'.join(map(str, sys.version_info[:3]))) + return python_version in requires_python_specifier + + +def get_metadata(dist): + # type: (Distribution) -> Message + if (isinstance(dist, pkg_resources.DistInfoDistribution) and + dist.has_metadata('METADATA')): + metadata = dist.get_metadata('METADATA') + elif dist.has_metadata('PKG-INFO'): + metadata = dist.get_metadata('PKG-INFO') + else: + logger.warning("No metadata found in %s", display_path(dist.location)) + metadata = '' + + feed_parser = FeedParser() + feed_parser.feed(metadata) + return feed_parser.close() + + +def check_dist_requires_python(dist): + pkg_info_dict = get_metadata(dist) + requires_python = pkg_info_dict.get('Requires-Python') + try: + if not check_requires_python(requires_python): + raise exceptions.UnsupportedPythonVersion( + "%s requires Python '%s' but the running Python is %s" % ( + dist.project_name, + requires_python, + '.'.join(map(str, sys.version_info[:3])),) + ) + except specifiers.InvalidSpecifier as e: + logger.warning( + "Package %s has an invalid Requires-Python entry %s - %s", + dist.project_name, requires_python, e, + ) + return + + +def get_installer(dist): + # type: (Distribution) -> str + if dist.has_metadata('INSTALLER'): + for line in dist.get_metadata_lines('INSTALLER'): + if line.strip(): + return line.strip() + return '' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/setuptools_build.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/setuptools_build.py new file mode 100644 index 0000000..03973e9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/setuptools_build.py @@ -0,0 +1,8 @@ +# Shim to wrap setup.py invocation with setuptools +SETUPTOOLS_SHIM = ( + "import setuptools, tokenize;__file__=%r;" + "f=getattr(tokenize, 'open', open)(__file__);" + "code=f.read().replace('\\r\\n', '\\n');" + "f.close();" + "exec(compile(code, __file__, 'exec'))" +) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/temp_dir.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/temp_dir.py new file mode 100644 index 0000000..2c81ad5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/temp_dir.py @@ -0,0 +1,155 @@ +from __future__ import absolute_import + +import errno +import itertools +import logging +import os.path +import tempfile + +from pip._internal.utils.misc import rmtree + +logger = logging.getLogger(__name__) + + +class TempDirectory(object): + """Helper class that owns and cleans up a temporary directory. + + This class can be used as a context manager or as an OO representation of a + temporary directory. + + Attributes: + path + Location to the created temporary directory or None + delete + Whether the directory should be deleted when exiting + (when used as a contextmanager) + + Methods: + create() + Creates a temporary directory and stores its path in the path + attribute. + cleanup() + Deletes the temporary directory and sets path attribute to None + + When used as a context manager, a temporary directory is created on + entering the context and, if the delete attribute is True, on exiting the + context the created directory is deleted. + """ + + def __init__(self, path=None, delete=None, kind="temp"): + super(TempDirectory, self).__init__() + + if path is None and delete is None: + # If we were not given an explicit directory, and we were not given + # an explicit delete option, then we'll default to deleting. + delete = True + + self.path = path + self.delete = delete + self.kind = kind + + def __repr__(self): + return "<{} {!r}>".format(self.__class__.__name__, self.path) + + def __enter__(self): + self.create() + return self + + def __exit__(self, exc, value, tb): + if self.delete: + self.cleanup() + + def create(self): + """Create a temporary directory and store its path in self.path + """ + if self.path is not None: + logger.debug( + "Skipped creation of temporary directory: {}".format(self.path) + ) + return + # We realpath here because some systems have their default tmpdir + # symlinked to another directory. This tends to confuse build + # scripts, so we canonicalize the path by traversing potential + # symlinks here. + self.path = os.path.realpath( + tempfile.mkdtemp(prefix="pip-{}-".format(self.kind)) + ) + logger.debug("Created temporary directory: {}".format(self.path)) + + def cleanup(self): + """Remove the temporary directory created and reset state + """ + if self.path is not None and os.path.exists(self.path): + rmtree(self.path) + self.path = None + + +class AdjacentTempDirectory(TempDirectory): + """Helper class that creates a temporary directory adjacent to a real one. + + Attributes: + original + The original directory to create a temp directory for. + path + After calling create() or entering, contains the full + path to the temporary directory. + delete + Whether the directory should be deleted when exiting + (when used as a contextmanager) + + """ + # The characters that may be used to name the temp directory + # We always prepend a ~ and then rotate through these until + # a usable name is found. + # pkg_resources raises a different error for .dist-info folder + # with leading '-' and invalid metadata + LEADING_CHARS = "-~.=%0123456789" + + def __init__(self, original, delete=None): + super(AdjacentTempDirectory, self).__init__(delete=delete) + self.original = original.rstrip('/\\') + + @classmethod + def _generate_names(cls, name): + """Generates a series of temporary names. + + The algorithm replaces the leading characters in the name + with ones that are valid filesystem characters, but are not + valid package names (for both Python and pip definitions of + package). + """ + for i in range(1, len(name)): + for candidate in itertools.combinations_with_replacement( + cls.LEADING_CHARS, i - 1): + new_name = '~' + ''.join(candidate) + name[i:] + if new_name != name: + yield new_name + + # If we make it this far, we will have to make a longer name + for i in range(len(cls.LEADING_CHARS)): + for candidate in itertools.combinations_with_replacement( + cls.LEADING_CHARS, i): + new_name = '~' + ''.join(candidate) + name + if new_name != name: + yield new_name + + def create(self): + root, name = os.path.split(self.original) + for candidate in self._generate_names(name): + path = os.path.join(root, candidate) + try: + os.mkdir(path) + except OSError as ex: + # Continue if the name exists already + if ex.errno != errno.EEXIST: + raise + else: + self.path = os.path.realpath(path) + break + + if not self.path: + # Final fallback on the default behavior. + self.path = os.path.realpath( + tempfile.mkdtemp(prefix="pip-{}-".format(self.kind)) + ) + logger.debug("Created temporary directory: {}".format(self.path)) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/typing.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/typing.py new file mode 100644 index 0000000..e085cdf --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/typing.py @@ -0,0 +1,29 @@ +"""For neatly implementing static typing in pip. + +`mypy` - the static type analysis tool we use - uses the `typing` module, which +provides core functionality fundamental to mypy's functioning. + +Generally, `typing` would be imported at runtime and used in that fashion - +it acts as a no-op at runtime and does not have any run-time overhead by +design. + +As it turns out, `typing` is not vendorable - it uses separate sources for +Python 2/Python 3. Thus, this codebase can not expect it to be present. +To work around this, mypy allows the typing import to be behind a False-y +optional to prevent it from running at runtime and type-comments can be used +to remove the need for the types to be accessible directly during runtime. + +This module provides the False-y guard in a nicely named fashion so that a +curious maintainer can reach here to read this. + +In pip, all static-typing related imports should be guarded as follows: + + from pip._internal.utils.typing import MYPY_CHECK_RUNNING + + if MYPY_CHECK_RUNNING: + from typing import ... # noqa: F401 + +Ref: https://github.com/python/mypy/issues/3216 +""" + +MYPY_CHECK_RUNNING = False diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/ui.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/ui.py new file mode 100644 index 0000000..433675d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/utils/ui.py @@ -0,0 +1,441 @@ +from __future__ import absolute_import, division + +import contextlib +import itertools +import logging +import sys +import time +from signal import SIGINT, default_int_handler, signal + +from pip._vendor import six +from pip._vendor.progress.bar import ( + Bar, ChargingBar, FillingCirclesBar, FillingSquaresBar, IncrementalBar, + ShadyBar, +) +from pip._vendor.progress.helpers import HIDE_CURSOR, SHOW_CURSOR, WritelnMixin +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 +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Any, Iterator, IO # noqa: F401 + +try: + from pip._vendor import colorama +# Lots of different errors can come from this, including SystemError and +# ImportError. +except Exception: + colorama = None + +logger = logging.getLogger(__name__) + + +def _select_progress_class(preferred, fallback): + 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", six.text_type()), + getattr(preferred, "fill", six.text_type()), + ] + 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: + six.text_type().join(characters).encode(encoding) + except UnicodeEncodeError: + return fallback + else: + return preferred + + +_BaseBar = _select_progress_class(IncrementalBar, Bar) # type: Any + + +class InterruptibleMixin(object): + """ + 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, **kwargs): + """ + Save the original SIGINT handler for later. + """ + super(InterruptibleMixin, self).__init__(*args, **kwargs) + + 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): + """ + Restore the original SIGINT handler after finishing. + + This should happen regardless of whether the progress display finishes + normally, or gets interrupted. + """ + super(InterruptibleMixin, self).finish() + signal(SIGINT, self.original_handler) + + def handle_sigint(self, signum, frame): + """ + 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): + pass + + +class BlueEmojiBar(IncrementalBar): + + suffix = "%(percent)d%%" + bar_prefix = " " + bar_suffix = " " + phases = (u"\U0001F539", u"\U0001F537", u"\U0001F535") # type: Any + + +class DownloadProgressMixin(object): + + def __init__(self, *args, **kwargs): + super(DownloadProgressMixin, self).__init__(*args, **kwargs) + self.message = (" " * (get_indentation() + 2)) + self.message + + @property + def downloaded(self): + return format_size(self.index) + + @property + def download_speed(self): + # Avoid zero division errors... + if self.avg == 0.0: + return "..." + return format_size(1 / self.avg) + "/s" + + @property + def pretty_eta(self): + if self.eta: + return "eta %s" % self.eta_td + return "" + + def iter(self, it, n=1): + for x in it: + yield x + self.next(n) + self.finish() + + +class WindowsMixin(object): + + def __init__(self, *args, **kwargs): + # 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 neds 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: + self.hide_cursor = False + + super(WindowsMixin, self).__init__(*args, **kwargs) + + # 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) + # 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" + +# NOTE: The "type: ignore" comments on the following classes are there to +# work around https://github.com/python/typing/issues/241 + + +class DefaultDownloadProgressBar(BaseDownloadProgressBar, + _BaseBar): + pass + + +class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): # type: ignore + pass + + +class DownloadIncrementalBar(BaseDownloadProgressBar, # type: ignore + IncrementalBar): + pass + + +class DownloadChargingBar(BaseDownloadProgressBar, # type: ignore + ChargingBar): + pass + + +class DownloadShadyBar(BaseDownloadProgressBar, ShadyBar): # type: ignore + pass + + +class DownloadFillingSquaresBar(BaseDownloadProgressBar, # type: ignore + FillingSquaresBar): + pass + + +class DownloadFillingCirclesBar(BaseDownloadProgressBar, # type: ignore + FillingCirclesBar): + pass + + +class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, # type: ignore + BlueEmojiBar): + pass + + +class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin, + DownloadProgressMixin, WritelnMixin, Spinner): + + file = sys.stdout + suffix = "%(downloaded)s %(download_speed)s" + + def next_phase(self): + if not hasattr(self, "_phaser"): + self._phaser = itertools.cycle(self.phases) + return next(self._phaser) + + def update(self): + message = self.message % self + phase = self.next_phase() + suffix = self.suffix % self + line = ''.join([ + message, + " " if message else "", + phase, + " " if suffix else "", + suffix, + ]) + + self.writeln(line) + + +BAR_TYPES = { + "off": (DownloadSilentBar, DownloadSilentBar), + "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), + "ascii": (DownloadIncrementalBar, DownloadProgressSpinner), + "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), + "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner) +} + + +def DownloadProgressProvider(progress_bar, max=None): + if max is None or max == 0: + return BAR_TYPES[progress_bar][1]().iter + else: + return BAR_TYPES[progress_bar][0](max=max).iter + + +################################################################ +# Generic "something is happening" spinners +# +# We don't even try using progress.spinner.Spinner here because it's actually +# simpler to reimplement from scratch than to coerce their code into doing +# what we need. +################################################################ + +@contextlib.contextmanager +def hidden_cursor(file): + # type: (IO) -> Iterator[None] + # The Windows terminal does not support the hide/show cursor ANSI codes, + # even via colorama. So don't even try. + if WINDOWS: + yield + # We don't want to clutter the output with control characters if we're + # writing to a file, or if the user is running with --quiet. + # See https://github.com/pypa/pip/issues/3418 + elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO: + yield + else: + file.write(HIDE_CURSOR) + try: + yield + finally: + file.write(SHOW_CURSOR) + + +class RateLimiter(object): + def __init__(self, min_update_interval_seconds): + # type: (float) -> None + self._min_update_interval_seconds = min_update_interval_seconds + self._last_update = 0 # type: float + + def ready(self): + # type: () -> bool + now = time.time() + delta = now - self._last_update + return delta >= self._min_update_interval_seconds + + def reset(self): + # type: () -> None + self._last_update = time.time() + + +class SpinnerInterface(object): + def spin(self): + # type: () -> None + raise NotImplementedError() + + def finish(self, final_status): + # type: (str) -> None + raise NotImplementedError() + + +class InteractiveSpinner(SpinnerInterface): + def __init__(self, message, file=None, spin_chars="-\\|/", + # Empirically, 8 updates/second looks nice + min_update_interval_seconds=0.125): + self._message = message + if file is None: + file = sys.stdout + self._file = file + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._finished = False + + self._spin_cycle = itertools.cycle(spin_chars) + + self._file.write(" " * get_indentation() + self._message + " ... ") + self._width = 0 + + def _write(self, status): + assert not self._finished + # Erase what we wrote before by backspacing to the beginning, writing + # spaces to overwrite the old text, and then backspacing again + backup = "\b" * self._width + self._file.write(backup + " " * self._width + backup) + # Now we have a blank slate to add our status + self._file.write(status) + self._width = len(status) + self._file.flush() + self._rate_limiter.reset() + + def spin(self): + # type: () -> None + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._write(next(self._spin_cycle)) + + def finish(self, final_status): + # type: (str) -> None + if self._finished: + return + self._write(final_status) + self._file.write("\n") + self._file.flush() + self._finished = True + + +# Used for dumb terminals, non-interactive installs (no tty), etc. +# We still print updates occasionally (once every 60 seconds by default) to +# act as a keep-alive for systems like Travis-CI that take lack-of-output as +# an indication that a task has frozen. +class NonInteractiveSpinner(SpinnerInterface): + def __init__(self, message, min_update_interval_seconds=60): + # type: (str, float) -> None + self._message = message + self._finished = False + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._update("started") + + def _update(self, status): + assert not self._finished + self._rate_limiter.reset() + logger.info("%s: %s", self._message, status) + + def spin(self): + # type: () -> None + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._update("still running...") + + def finish(self, final_status): + # type: (str) -> None + if self._finished: + return + self._update("finished with status '%s'" % (final_status,)) + self._finished = True + + +@contextlib.contextmanager +def open_spinner(message): + # type: (str) -> Iterator[SpinnerInterface] + # 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. + # Non-interactive spinner goes through the logging system, so it is always + # in sync with logging configuration. + if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO: + spinner = InteractiveSpinner(message) # type: SpinnerInterface + else: + spinner = NonInteractiveSpinner(message) + try: + with hidden_cursor(sys.stdout): + yield spinner + except KeyboardInterrupt: + spinner.finish("canceled") + raise + except Exception: + spinner.finish("error") + raise + else: + spinner.finish("done") diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/__init__.py new file mode 100644 index 0000000..9cba764 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/__init__.py @@ -0,0 +1,534 @@ +"""Handles all VCS (version control) support""" +from __future__ import absolute_import + +import errno +import logging +import os +import shutil +import sys + +from pip._vendor.six.moves.urllib import parse as urllib_parse + +from pip._internal.exceptions import BadCommand +from pip._internal.utils.misc import ( + display_path, backup_dir, call_subprocess, rmtree, ask_path_exists, +) +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Any, Dict, Iterable, List, Mapping, Optional, Text, Tuple, Type + ) + from pip._internal.utils.ui import SpinnerInterface # noqa: F401 + + AuthInfo = Tuple[Optional[str], Optional[str]] + +__all__ = ['vcs'] + + +logger = logging.getLogger(__name__) + + +class RemoteNotFoundError(Exception): + pass + + +class RevOptions(object): + + """ + Encapsulates a VCS-specific revision to install, along with any VCS + install options. + + Instances of this class should be treated as if immutable. + """ + + def __init__(self, vcs, rev=None, extra_args=None): + # type: (VersionControl, Optional[str], Optional[List[str]]) -> None + """ + Args: + vcs: a VersionControl object. + rev: the name of the revision to install. + extra_args: a list of extra options. + """ + if extra_args is None: + extra_args = [] + + self.extra_args = extra_args + self.rev = rev + self.vcs = vcs + + def __repr__(self): + return '<RevOptions {}: rev={!r}>'.format(self.vcs.name, self.rev) + + @property + def arg_rev(self): + # type: () -> Optional[str] + if self.rev is None: + return self.vcs.default_arg_rev + + return self.rev + + def to_args(self): + # type: () -> List[str] + """ + Return the VCS-specific command arguments. + """ + args = [] # type: List[str] + rev = self.arg_rev + if rev is not None: + args += self.vcs.get_base_rev_args(rev) + args += self.extra_args + + return args + + def to_display(self): + # type: () -> str + if not self.rev: + return '' + + return ' (to revision {})'.format(self.rev) + + def make_new(self, rev): + # type: (str) -> RevOptions + """ + Make a copy of the current instance, but with a new rev. + + Args: + rev: the name of the revision for the new object. + """ + return self.vcs.make_rev_options(rev, extra_args=self.extra_args) + + +class VcsSupport(object): + _registry = {} # type: Dict[str, Type[VersionControl]] + schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn'] + + def __init__(self): + # type: () -> None + # Register more schemes with urlparse for various version control + # systems + urllib_parse.uses_netloc.extend(self.schemes) + # Python >= 2.7.4, 3.3 doesn't have uses_fragment + if getattr(urllib_parse, 'uses_fragment', None): + urllib_parse.uses_fragment.extend(self.schemes) + super(VcsSupport, self).__init__() + + def __iter__(self): + return self._registry.__iter__() + + @property + def backends(self): + # type: () -> List[Type[VersionControl]] + return list(self._registry.values()) + + @property + def dirnames(self): + # type: () -> List[str] + return [backend.dirname for backend in self.backends] + + @property + def all_schemes(self): + # type: () -> List[str] + schemes = [] # type: List[str] + for backend in self.backends: + schemes.extend(backend.schemes) + return schemes + + def register(self, cls): + # type: (Type[VersionControl]) -> None + if not hasattr(cls, 'name'): + logger.warning('Cannot register VCS %s', cls.__name__) + return + if cls.name not in self._registry: + self._registry[cls.name] = cls + logger.debug('Registered VCS backend: %s', cls.name) + + def unregister(self, cls=None, name=None): + # type: (Optional[Type[VersionControl]], Optional[str]) -> None + if name in self._registry: + del self._registry[name] + elif cls in self._registry.values(): + del self._registry[cls.name] + else: + logger.warning('Cannot unregister because no class or name given') + + def get_backend_type(self, location): + # type: (str) -> Optional[Type[VersionControl]] + """ + Return the type of the version control backend if found at given + location, e.g. vcs.get_backend_type('/path/to/vcs/checkout') + """ + for vc_type in self._registry.values(): + if vc_type.controls_location(location): + logger.debug('Determine that %s uses VCS: %s', + location, vc_type.name) + return vc_type + return None + + def get_backend(self, name): + # type: (str) -> Optional[Type[VersionControl]] + name = name.lower() + if name in self._registry: + return self._registry[name] + return None + + +vcs = VcsSupport() + + +class VersionControl(object): + name = '' + dirname = '' + repo_name = '' + # List of supported schemes for this Version Control + schemes = () # type: Tuple[str, ...] + # Iterable of environment variable names to pass to call_subprocess(). + unset_environ = () # type: Tuple[str, ...] + default_arg_rev = None # type: Optional[str] + + def __init__(self, url=None, *args, **kwargs): + self.url = url + super(VersionControl, self).__init__(*args, **kwargs) + + def get_base_rev_args(self, rev): + """ + Return the base revision arguments for a vcs command. + + Args: + rev: the name of a revision to install. Cannot be None. + """ + raise NotImplementedError + + def make_rev_options(self, rev=None, extra_args=None): + # type: (Optional[str], Optional[List[str]]) -> RevOptions + """ + Return a RevOptions object. + + Args: + rev: the name of a revision to install. + extra_args: a list of extra options. + """ + return RevOptions(self, rev, extra_args=extra_args) + + @classmethod + def _is_local_repository(cls, repo): + # type: (str) -> bool + """ + posix absolute paths start with os.path.sep, + win32 ones start with drive (like c:\\folder) + """ + drive, tail = os.path.splitdrive(repo) + return repo.startswith(os.path.sep) or bool(drive) + + def export(self, location): + """ + Export the repository at the url to the destination location + i.e. only download the files, without vcs informations + """ + raise NotImplementedError + + def get_netloc_and_auth(self, netloc, scheme): + """ + Parse the repository URL's netloc, and return the new netloc to use + along with auth information. + + Args: + netloc: the original repository URL netloc. + scheme: the repository URL's scheme without the vcs prefix. + + This is mainly for the Subversion class to override, so that auth + information can be provided via the --username and --password options + instead of through the URL. For other subclasses like Git without + such an option, auth information must stay in the URL. + + Returns: (netloc, (username, password)). + """ + return netloc, (None, None) + + def get_url_rev_and_auth(self, url): + # type: (str) -> Tuple[str, Optional[str], AuthInfo] + """ + Parse the repository URL to use, and return the URL, revision, + and auth info to use. + + Returns: (url, rev, (username, password)). + """ + scheme, netloc, path, query, frag = urllib_parse.urlsplit(url) + if '+' not in scheme: + raise ValueError( + "Sorry, {!r} is a malformed VCS url. " + "The format is <vcs>+<protocol>://<url>, " + "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url) + ) + # Remove the vcs prefix. + scheme = scheme.split('+', 1)[1] + netloc, user_pass = self.get_netloc_and_auth(netloc, scheme) + rev = None + if '@' in path: + path, rev = path.rsplit('@', 1) + url = urllib_parse.urlunsplit((scheme, netloc, path, query, '')) + return url, rev, user_pass + + def make_rev_args(self, username, password): + """ + Return the RevOptions "extra arguments" to use in obtain(). + """ + return [] + + def get_url_rev_options(self, url): + # type: (str) -> Tuple[str, RevOptions] + """ + Return the URL and RevOptions object to use in obtain() and in + some cases export(), as a tuple (url, rev_options). + """ + url, rev, user_pass = self.get_url_rev_and_auth(url) + username, password = user_pass + extra_args = self.make_rev_args(username, password) + rev_options = self.make_rev_options(rev, extra_args=extra_args) + + return url, rev_options + + def normalize_url(self, url): + # type: (str) -> str + """ + Normalize a URL for comparison by unquoting it and removing any + trailing slash. + """ + return urllib_parse.unquote(url).rstrip('/') + + def compare_urls(self, url1, url2): + # type: (str, str) -> bool + """ + Compare two repo URLs for identity, ignoring incidental differences. + """ + return (self.normalize_url(url1) == self.normalize_url(url2)) + + def fetch_new(self, dest, url, rev_options): + """ + Fetch a revision from a repository, in the case that this is the + first fetch from the repository. + + Args: + dest: the directory to fetch the repository to. + rev_options: a RevOptions object. + """ + raise NotImplementedError + + def switch(self, dest, url, rev_options): + """ + Switch the repo at ``dest`` to point to ``URL``. + + Args: + rev_options: a RevOptions object. + """ + raise NotImplementedError + + def update(self, dest, url, rev_options): + """ + Update an already-existing repo to the given ``rev_options``. + + Args: + rev_options: a RevOptions object. + """ + raise NotImplementedError + + def is_commit_id_equal(self, dest, name): + """ + Return whether the id of the current commit equals the given name. + + Args: + dest: the repository directory. + name: a string name. + """ + raise NotImplementedError + + def obtain(self, dest): + # type: (str) -> None + """ + Install or update in editable mode the package represented by this + VersionControl object. + + Args: + dest: the repository directory in which to install or update. + """ + url, rev_options = self.get_url_rev_options(self.url) + + if not os.path.exists(dest): + self.fetch_new(dest, url, rev_options) + return + + rev_display = rev_options.to_display() + if self.is_repository_directory(dest): + existing_url = self.get_remote_url(dest) + if self.compare_urls(existing_url, url): + logger.debug( + '%s in %s exists, and has correct URL (%s)', + self.repo_name.title(), + display_path(dest), + url, + ) + if not self.is_commit_id_equal(dest, rev_options.rev): + logger.info( + 'Updating %s %s%s', + display_path(dest), + self.repo_name, + rev_display, + ) + self.update(dest, url, rev_options) + else: + logger.info('Skipping because already up-to-date.') + return + + logger.warning( + '%s %s in %s exists with URL %s', + self.name, + self.repo_name, + display_path(dest), + existing_url, + ) + prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', + ('s', 'i', 'w', 'b')) + else: + logger.warning( + 'Directory %s already exists, and is not a %s %s.', + dest, + self.name, + self.repo_name, + ) + # https://github.com/python/mypy/issues/1174 + prompt = ('(i)gnore, (w)ipe, (b)ackup ', # type: ignore + ('i', 'w', 'b')) + + logger.warning( + 'The plan is to install the %s repository %s', + self.name, + url, + ) + response = ask_path_exists('What to do? %s' % prompt[0], prompt[1]) + + if response == 'a': + sys.exit(-1) + + if response == 'w': + logger.warning('Deleting %s', display_path(dest)) + rmtree(dest) + self.fetch_new(dest, url, rev_options) + 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) + return + + # Do nothing if the response is "i". + if response == 's': + logger.info( + 'Switching %s %s to %s%s', + self.repo_name, + display_path(dest), + url, + rev_display, + ) + self.switch(dest, url, rev_options) + + def unpack(self, location): + # type: (str) -> None + """ + Clean up current location and download the url repository + (and vcs infos) into location + """ + if os.path.exists(location): + rmtree(location) + self.obtain(location) + + @classmethod + def get_src_requirement(cls, location, project_name): + """ + Return a string representing the requirement needed to + redownload the files currently present in location, something + like: + {repository_url}@{revision}#egg={project_name}-{version_identifier} + """ + raise NotImplementedError + + @classmethod + def get_remote_url(cls, location): + """ + Return the url used at location + + Raises RemoteNotFoundError if the repository does not have a remote + url configured. + """ + raise NotImplementedError + + @classmethod + def get_revision(cls, location): + """ + Return the current commit id of the files at the given location. + """ + raise NotImplementedError + + @classmethod + def run_command( + cls, + cmd, # type: List[str] + show_stdout=True, # type: bool + cwd=None, # type: Optional[str] + on_returncode='raise', # type: str + extra_ok_returncodes=None, # type: Optional[Iterable[int]] + command_desc=None, # type: Optional[str] + extra_environ=None, # type: Optional[Mapping[str, Any]] + spinner=None # type: Optional[SpinnerInterface] + ): + # type: (...) -> Optional[Text] + """ + Run a VCS subcommand + This is simply a wrapper around call_subprocess that adds the VCS + command name, and checks that the VCS is available + """ + cmd = [cls.name] + cmd + try: + return call_subprocess(cmd, show_stdout, cwd, + on_returncode=on_returncode, + extra_ok_returncodes=extra_ok_returncodes, + command_desc=command_desc, + extra_environ=extra_environ, + unset_environ=cls.unset_environ, + spinner=spinner) + except OSError as e: + # errno.ENOENT = no such file or directory + # In other words, the VCS executable isn't available + if e.errno == errno.ENOENT: + raise BadCommand( + 'Cannot find command %r - do you have ' + '%r installed and in your ' + 'PATH?' % (cls.name, cls.name)) + else: + raise # re-raise exception if a different error occurred + + @classmethod + def is_repository_directory(cls, path): + # type: (str) -> bool + """ + Return whether a directory path is a repository directory. + """ + logger.debug('Checking in %s for %s (%s)...', + path, cls.dirname, cls.name) + return os.path.exists(os.path.join(path, cls.dirname)) + + @classmethod + def controls_location(cls, location): + # type: (str) -> bool + """ + Check if a location is controlled by the vcs. + It is meant to be overridden to implement smarter detection + mechanisms for specific vcs. + + This can do more than is_repository_directory() alone. For example, + the Git override checks that Git is actually available. + """ + return cls.is_repository_directory(location) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/bazaar.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/bazaar.py new file mode 100644 index 0000000..4c6ac79 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/bazaar.py @@ -0,0 +1,114 @@ +from __future__ import absolute_import + +import logging +import os + +from pip._vendor.six.moves.urllib import parse as urllib_parse + +from pip._internal.download import path_to_url +from pip._internal.utils.misc import ( + display_path, make_vcs_requirement_url, rmtree, +) +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.vcs import VersionControl, vcs + +logger = logging.getLogger(__name__) + + +class Bazaar(VersionControl): + name = 'bzr' + dirname = '.bzr' + repo_name = 'branch' + schemes = ( + 'bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp', + 'bzr+lp', + ) + + def __init__(self, url=None, *args, **kwargs): + super(Bazaar, self).__init__(url, *args, **kwargs) + # This is only needed for python <2.7.5 + # Register lp but do not expose as a scheme to support bzr+lp. + if getattr(urllib_parse, 'uses_fragment', None): + urllib_parse.uses_fragment.extend(['lp']) + + def get_base_rev_args(self, rev): + return ['-r', rev] + + def export(self, location): + """ + Export the Bazaar repository at the url to the destination location + """ + # Remove the location to make sure Bazaar can export it correctly + if os.path.exists(location): + rmtree(location) + + with TempDirectory(kind="export") as temp_dir: + self.unpack(temp_dir.path) + + self.run_command( + ['export', location], + cwd=temp_dir.path, show_stdout=False, + ) + + def fetch_new(self, dest, url, rev_options): + rev_display = rev_options.to_display() + logger.info( + 'Checking out %s%s to %s', + url, + rev_display, + display_path(dest), + ) + cmd_args = ['branch', '-q'] + rev_options.to_args() + [url, dest] + self.run_command(cmd_args) + + def switch(self, dest, url, rev_options): + self.run_command(['switch', url], cwd=dest) + + def update(self, dest, url, rev_options): + cmd_args = ['pull', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + + def get_url_rev_and_auth(self, url): + # hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it + url, rev, user_pass = super(Bazaar, self).get_url_rev_and_auth(url) + if url.startswith('ssh://'): + url = 'bzr+' + url + return url, rev, user_pass + + @classmethod + def get_remote_url(cls, location): + urls = cls.run_command(['info'], show_stdout=False, cwd=location) + for line in urls.splitlines(): + line = line.strip() + for x in ('checkout of branch: ', + 'parent branch: '): + if line.startswith(x): + repo = line.split(x)[1] + if cls._is_local_repository(repo): + return path_to_url(repo) + return repo + return None + + @classmethod + def get_revision(cls, location): + revision = cls.run_command( + ['revno'], show_stdout=False, cwd=location, + ) + return revision.splitlines()[-1] + + @classmethod + def get_src_requirement(cls, location, project_name): + repo = cls.get_remote_url(location) + if not repo: + return None + if not repo.lower().startswith('bzr:'): + repo = 'bzr+' + repo + current_rev = cls.get_revision(location) + return make_vcs_requirement_url(repo, current_rev, project_name) + + def is_commit_id_equal(self, dest, name): + """Always assume the versions don't match""" + return False + + +vcs.register(Bazaar) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/git.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/git.py new file mode 100644 index 0000000..dd2bd61 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/git.py @@ -0,0 +1,369 @@ +from __future__ import absolute_import + +import logging +import os.path +import re + +from pip._vendor.packaging.version import parse as parse_version +from pip._vendor.six.moves.urllib import parse as urllib_parse +from pip._vendor.six.moves.urllib import request as urllib_request + +from pip._internal.exceptions import BadCommand +from pip._internal.utils.compat import samefile +from pip._internal.utils.misc import ( + display_path, make_vcs_requirement_url, redact_password_from_url, +) +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.vcs import RemoteNotFoundError, VersionControl, vcs + +urlsplit = urllib_parse.urlsplit +urlunsplit = urllib_parse.urlunsplit + + +logger = logging.getLogger(__name__) + + +HASH_REGEX = re.compile('[a-fA-F0-9]{40}') + + +def looks_like_hash(sha): + return bool(HASH_REGEX.match(sha)) + + +class Git(VersionControl): + name = 'git' + dirname = '.git' + repo_name = 'clone' + schemes = ( + 'git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file', + ) + # Prevent the user's environment variables from interfering with pip: + # https://github.com/pypa/pip/issues/1130 + unset_environ = ('GIT_DIR', 'GIT_WORK_TREE') + default_arg_rev = 'HEAD' + + def __init__(self, url=None, *args, **kwargs): + + # Works around an apparent Git bug + # (see https://article.gmane.org/gmane.comp.version-control.git/146500) + if url: + scheme, netloc, path, query, fragment = urlsplit(url) + if scheme.endswith('file'): + initial_slashes = path[:-len(path.lstrip('/'))] + newpath = ( + initial_slashes + + urllib_request.url2pathname(path) + .replace('\\', '/').lstrip('/') + ) + url = urlunsplit((scheme, netloc, newpath, query, fragment)) + after_plus = scheme.find('+') + 1 + url = scheme[:after_plus] + urlunsplit( + (scheme[after_plus:], netloc, newpath, query, fragment), + ) + + super(Git, self).__init__(url, *args, **kwargs) + + def get_base_rev_args(self, rev): + return [rev] + + def get_git_version(self): + VERSION_PFX = 'git version ' + version = self.run_command(['version'], show_stdout=False) + if version.startswith(VERSION_PFX): + version = version[len(VERSION_PFX):].split()[0] + else: + version = '' + # get first 3 positions of the git version becasue + # on windows it is x.y.z.windows.t, and this parses as + # LegacyVersion which always smaller than a Version. + version = '.'.join(version.split('.')[:3]) + return parse_version(version) + + def get_current_branch(self, location): + """ + Return the current branch, or None if HEAD isn't at a branch + (e.g. detached HEAD). + """ + # git-symbolic-ref exits with empty stdout if "HEAD" is a detached + # HEAD rather than a symbolic ref. In addition, the -q causes the + # command to exit with status code 1 instead of 128 in this case + # and to suppress the message to stderr. + args = ['symbolic-ref', '-q', 'HEAD'] + output = self.run_command( + args, extra_ok_returncodes=(1, ), show_stdout=False, cwd=location, + ) + ref = output.strip() + + if ref.startswith('refs/heads/'): + return ref[len('refs/heads/'):] + + return None + + def export(self, location): + """Export the Git repository at the url to the destination location""" + if not location.endswith('/'): + location = location + '/' + + with TempDirectory(kind="export") as temp_dir: + self.unpack(temp_dir.path) + self.run_command( + ['checkout-index', '-a', '-f', '--prefix', location], + show_stdout=False, cwd=temp_dir.path + ) + + def get_revision_sha(self, dest, rev): + """ + Return (sha_or_none, is_branch), where sha_or_none is a commit hash + if the revision names a remote branch or tag, otherwise None. + + Args: + dest: the repository directory. + rev: the revision name. + """ + # Pass rev to pre-filter the list. + output = self.run_command(['show-ref', rev], cwd=dest, + show_stdout=False, on_returncode='ignore') + refs = {} + for line in output.strip().splitlines(): + try: + sha, ref = line.split() + except ValueError: + # Include the offending line to simplify troubleshooting if + # this error ever occurs. + raise ValueError('unexpected show-ref line: {!r}'.format(line)) + + refs[ref] = sha + + branch_ref = 'refs/remotes/origin/{}'.format(rev) + tag_ref = 'refs/tags/{}'.format(rev) + + sha = refs.get(branch_ref) + if sha is not None: + return (sha, True) + + sha = refs.get(tag_ref) + + return (sha, False) + + def resolve_revision(self, dest, url, rev_options): + """ + Resolve a revision to a new RevOptions object with the SHA1 of the + branch, tag, or ref if found. + + Args: + rev_options: a RevOptions object. + """ + rev = rev_options.arg_rev + sha, is_branch = self.get_revision_sha(dest, rev) + + if sha is not None: + rev_options = rev_options.make_new(sha) + rev_options.branch_name = rev if is_branch else None + + return rev_options + + # Do not show a warning for the common case of something that has + # the form of a Git commit hash. + if not looks_like_hash(rev): + logger.warning( + "Did not find branch or tag '%s', assuming revision or ref.", + rev, + ) + + if not rev.startswith('refs/'): + return rev_options + + # If it looks like a ref, we have to fetch it explicitly. + self.run_command( + ['fetch', '-q', url] + rev_options.to_args(), + cwd=dest, + ) + # Change the revision to the SHA of the ref we fetched + sha = self.get_revision(dest, rev='FETCH_HEAD') + rev_options = rev_options.make_new(sha) + + return rev_options + + def is_commit_id_equal(self, dest, name): + """ + Return whether the current commit hash equals the given name. + + Args: + dest: the repository directory. + name: a string name. + """ + if not name: + # Then avoid an unnecessary subprocess call. + return False + + return self.get_revision(dest) == name + + def fetch_new(self, dest, url, rev_options): + rev_display = rev_options.to_display() + logger.info( + 'Cloning %s%s to %s', redact_password_from_url(url), + rev_display, display_path(dest), + ) + self.run_command(['clone', '-q', url, dest]) + + if rev_options.rev: + # Then a specific revision was requested. + rev_options = self.resolve_revision(dest, url, rev_options) + branch_name = getattr(rev_options, 'branch_name', None) + if branch_name is None: + # Only do a checkout if the current commit id doesn't match + # the requested revision. + if not self.is_commit_id_equal(dest, rev_options.rev): + cmd_args = ['checkout', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + elif self.get_current_branch(dest) != branch_name: + # Then a specific branch was requested, and that branch + # is not yet checked out. + track_branch = 'origin/{}'.format(branch_name) + cmd_args = [ + 'checkout', '-b', branch_name, '--track', track_branch, + ] + self.run_command(cmd_args, cwd=dest) + + #: repo may contain submodules + self.update_submodules(dest) + + def switch(self, dest, url, rev_options): + self.run_command(['config', 'remote.origin.url', url], cwd=dest) + cmd_args = ['checkout', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + + self.update_submodules(dest) + + def update(self, dest, url, rev_options): + # First fetch changes from the default remote + if self.get_git_version() >= parse_version('1.9.0'): + # fetch tags in addition to everything else + self.run_command(['fetch', '-q', '--tags'], cwd=dest) + else: + self.run_command(['fetch', '-q'], cwd=dest) + # Then reset to wanted revision (maybe even origin/master) + rev_options = self.resolve_revision(dest, url, rev_options) + cmd_args = ['reset', '--hard', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + #: update submodules + self.update_submodules(dest) + + @classmethod + def get_remote_url(cls, location): + """ + Return URL of the first remote encountered. + + Raises RemoteNotFoundError if the repository does not have a remote + url configured. + """ + # We need to pass 1 for extra_ok_returncodes since the command + # exits with return code 1 if there are no matching lines. + stdout = cls.run_command( + ['config', '--get-regexp', r'remote\..*\.url'], + extra_ok_returncodes=(1, ), show_stdout=False, cwd=location, + ) + remotes = stdout.splitlines() + try: + found_remote = remotes[0] + except IndexError: + raise RemoteNotFoundError + + for remote in remotes: + if remote.startswith('remote.origin.url '): + found_remote = remote + break + url = found_remote.split(' ')[1] + return url.strip() + + @classmethod + def get_revision(cls, location, rev=None): + if rev is None: + rev = 'HEAD' + current_rev = cls.run_command( + ['rev-parse', rev], show_stdout=False, cwd=location, + ) + return current_rev.strip() + + @classmethod + def _get_subdirectory(cls, location): + """Return the relative path of setup.py to the git repo root.""" + # find the repo root + git_dir = cls.run_command(['rev-parse', '--git-dir'], + show_stdout=False, cwd=location).strip() + if not os.path.isabs(git_dir): + git_dir = os.path.join(location, git_dir) + root_dir = os.path.join(git_dir, '..') + # find setup.py + orig_location = location + while not os.path.exists(os.path.join(location, 'setup.py')): + last_location = location + location = os.path.dirname(location) + if location == last_location: + # We've traversed up to the root of the filesystem without + # finding setup.py + logger.warning( + "Could not find setup.py for directory %s (tried all " + "parent directories)", + orig_location, + ) + return None + # relative path of setup.py to repo root + if samefile(root_dir, location): + return None + return os.path.relpath(location, root_dir) + + @classmethod + def get_src_requirement(cls, location, project_name): + repo = cls.get_remote_url(location) + if not repo.lower().startswith('git:'): + repo = 'git+' + repo + current_rev = cls.get_revision(location) + subdir = cls._get_subdirectory(location) + req = make_vcs_requirement_url(repo, current_rev, project_name, + subdir=subdir) + + return req + + def get_url_rev_and_auth(self, url): + """ + Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'. + That's required because although they use SSH they sometimes don't + work with a ssh:// scheme (e.g. GitHub). But we need a scheme for + parsing. Hence we remove it again afterwards and return it as a stub. + """ + if '://' not in url: + assert 'file:' not in url + url = url.replace('git+', 'git+ssh://') + url, rev, user_pass = super(Git, self).get_url_rev_and_auth(url) + url = url.replace('ssh://', '') + else: + url, rev, user_pass = super(Git, self).get_url_rev_and_auth(url) + + return url, rev, user_pass + + def update_submodules(self, location): + if not os.path.exists(os.path.join(location, '.gitmodules')): + return + self.run_command( + ['submodule', 'update', '--init', '--recursive', '-q'], + cwd=location, + ) + + @classmethod + def controls_location(cls, location): + if super(Git, cls).controls_location(location): + return True + try: + r = cls.run_command(['rev-parse'], + cwd=location, + show_stdout=False, + on_returncode='ignore') + return not r + except BadCommand: + logger.debug("could not determine if %s is under git control " + "because git is not available", location) + return False + + +vcs.register(Git) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/mercurial.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/mercurial.py new file mode 100644 index 0000000..26e75de --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/mercurial.py @@ -0,0 +1,103 @@ +from __future__ import absolute_import + +import logging +import os + +from pip._vendor.six.moves import configparser + +from pip._internal.download import path_to_url +from pip._internal.utils.misc import display_path, make_vcs_requirement_url +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.vcs import VersionControl, vcs + +logger = logging.getLogger(__name__) + + +class Mercurial(VersionControl): + name = 'hg' + dirname = '.hg' + repo_name = 'clone' + schemes = ('hg', 'hg+http', 'hg+https', 'hg+ssh', 'hg+static-http') + + def get_base_rev_args(self, rev): + return [rev] + + def export(self, location): + """Export the Hg repository at the url to the destination location""" + with TempDirectory(kind="export") as temp_dir: + self.unpack(temp_dir.path) + + self.run_command( + ['archive', location], show_stdout=False, cwd=temp_dir.path + ) + + def fetch_new(self, dest, url, rev_options): + rev_display = rev_options.to_display() + logger.info( + 'Cloning hg %s%s to %s', + url, + rev_display, + display_path(dest), + ) + self.run_command(['clone', '--noupdate', '-q', url, dest]) + cmd_args = ['update', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + + def switch(self, dest, url, rev_options): + repo_config = os.path.join(dest, self.dirname, 'hgrc') + config = configparser.SafeConfigParser() + try: + config.read(repo_config) + config.set('paths', 'default', url) + with open(repo_config, 'w') as config_file: + config.write(config_file) + except (OSError, configparser.NoSectionError) as exc: + logger.warning( + 'Could not switch Mercurial repository to %s: %s', url, exc, + ) + else: + cmd_args = ['update', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + + def update(self, dest, url, rev_options): + self.run_command(['pull', '-q'], cwd=dest) + cmd_args = ['update', '-q'] + rev_options.to_args() + self.run_command(cmd_args, cwd=dest) + + @classmethod + def get_remote_url(cls, location): + url = cls.run_command( + ['showconfig', 'paths.default'], + show_stdout=False, cwd=location).strip() + if cls._is_local_repository(url): + url = path_to_url(url) + return url.strip() + + @classmethod + def get_revision(cls, location): + current_revision = cls.run_command( + ['parents', '--template={rev}'], + show_stdout=False, cwd=location).strip() + return current_revision + + @classmethod + def get_revision_hash(cls, location): + current_rev_hash = cls.run_command( + ['parents', '--template={node}'], + show_stdout=False, cwd=location).strip() + return current_rev_hash + + @classmethod + def get_src_requirement(cls, location, project_name): + repo = cls.get_remote_url(location) + if not repo.lower().startswith('hg:'): + repo = 'hg+' + repo + current_rev_hash = cls.get_revision_hash(location) + return make_vcs_requirement_url(repo, current_rev_hash, project_name) + + def is_commit_id_equal(self, dest, name): + """Always assume the versions don't match""" + return False + + +vcs.register(Mercurial) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/subversion.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/subversion.py new file mode 100644 index 0000000..42ac5ac --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/vcs/subversion.py @@ -0,0 +1,200 @@ +from __future__ import absolute_import + +import logging +import os +import re + +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + display_path, make_vcs_requirement_url, rmtree, split_auth_from_netloc, +) +from pip._internal.vcs import VersionControl, vcs + +_svn_xml_url_re = re.compile('url="([^"]+)"') +_svn_rev_re = re.compile(r'committed-rev="(\d+)"') +_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"') +_svn_info_xml_url_re = re.compile(r'<url>(.*)</url>') + + +logger = logging.getLogger(__name__) + + +class Subversion(VersionControl): + name = 'svn' + dirname = '.svn' + repo_name = 'checkout' + schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn') + + def get_base_rev_args(self, rev): + return ['-r', rev] + + def export(self, location): + """Export the svn repository at the url to the destination location""" + url, rev_options = self.get_url_rev_options(self.url) + + logger.info('Exporting svn repository %s to %s', url, location) + with indent_log(): + if os.path.exists(location): + # Subversion doesn't like to check out over an existing + # directory --force fixes this, but was only added in svn 1.5 + rmtree(location) + cmd_args = ['export'] + rev_options.to_args() + [url, location] + self.run_command(cmd_args, show_stdout=False) + + def fetch_new(self, dest, url, rev_options): + rev_display = rev_options.to_display() + logger.info( + 'Checking out %s%s to %s', + url, + rev_display, + display_path(dest), + ) + cmd_args = ['checkout', '-q'] + rev_options.to_args() + [url, dest] + self.run_command(cmd_args) + + def switch(self, dest, url, rev_options): + cmd_args = ['switch'] + rev_options.to_args() + [url, dest] + self.run_command(cmd_args) + + def update(self, dest, url, rev_options): + cmd_args = ['update'] + rev_options.to_args() + [dest] + self.run_command(cmd_args) + + @classmethod + def get_revision(cls, location): + """ + Return the maximum revision for all files under a given location + """ + # Note: taken from setuptools.command.egg_info + revision = 0 + + for base, dirs, files in os.walk(location): + if cls.dirname not in dirs: + dirs[:] = [] + continue # no sense walking uncontrolled subdirs + dirs.remove(cls.dirname) + entries_fn = os.path.join(base, cls.dirname, 'entries') + if not os.path.exists(entries_fn): + # FIXME: should we warn? + continue + + dirurl, localrev = cls._get_svn_url_rev(base) + + if base == location: + base = dirurl + '/' # save the root url + elif not dirurl or not dirurl.startswith(base): + dirs[:] = [] + continue # not part of the same svn tree, skip it + revision = max(revision, localrev) + return revision + + def get_netloc_and_auth(self, netloc, scheme): + """ + This override allows the auth information to be passed to svn via the + --username and --password options instead of via the URL. + """ + if scheme == 'ssh': + # The --username and --password options can't be used for + # svn+ssh URLs, so keep the auth information in the URL. + return super(Subversion, self).get_netloc_and_auth( + netloc, scheme) + + return split_auth_from_netloc(netloc) + + def get_url_rev_and_auth(self, url): + # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it + url, rev, user_pass = super(Subversion, self).get_url_rev_and_auth(url) + if url.startswith('ssh://'): + url = 'svn+' + url + return url, rev, user_pass + + def make_rev_args(self, username, password): + extra_args = [] + if username: + extra_args += ['--username', username] + if password: + extra_args += ['--password', password] + + return extra_args + + @classmethod + def get_remote_url(cls, location): + # In cases where the source is in a subdirectory, not alongside + # setup.py we have to look up in the location until we find a real + # setup.py + orig_location = location + while not os.path.exists(os.path.join(location, 'setup.py')): + last_location = location + location = os.path.dirname(location) + if location == last_location: + # We've traversed up to the root of the filesystem without + # finding setup.py + logger.warning( + "Could not find setup.py for directory %s (tried all " + "parent directories)", + orig_location, + ) + return None + + return cls._get_svn_url_rev(location)[0] + + @classmethod + def _get_svn_url_rev(cls, location): + from pip._internal.exceptions import InstallationError + + entries_path = os.path.join(location, cls.dirname, 'entries') + if os.path.exists(entries_path): + with open(entries_path) as f: + data = f.read() + else: # subversion >= 1.7 does not have the 'entries' file + data = '' + + if (data.startswith('8') or + data.startswith('9') or + data.startswith('10')): + data = list(map(str.splitlines, data.split('\n\x0c\n'))) + del data[0][0] # get rid of the '8' + url = data[0][3] + revs = [int(d[9]) for d in data if len(d) > 9 and d[9]] + [0] + elif data.startswith('<?xml'): + match = _svn_xml_url_re.search(data) + if not match: + raise ValueError('Badly formatted data: %r' % data) + url = match.group(1) # get repository URL + revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)] + [0] + else: + try: + # subversion >= 1.7 + xml = cls.run_command( + ['info', '--xml', location], + show_stdout=False, + ) + url = _svn_info_xml_url_re.search(xml).group(1) + revs = [ + int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml) + ] + except InstallationError: + url, revs = None, [] + + if revs: + rev = max(revs) + else: + rev = 0 + + return url, rev + + @classmethod + def get_src_requirement(cls, location, project_name): + repo = cls.get_remote_url(location) + if repo is None: + return None + repo = 'svn+' + repo + rev = cls.get_revision(location) + return make_vcs_requirement_url(repo, rev, project_name) + + def is_commit_id_equal(self, dest, name): + """Always assume the versions don't match""" + return False + + +vcs.register(Subversion) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/wheel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/wheel.py new file mode 100644 index 0000000..67bcc7f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_internal/wheel.py @@ -0,0 +1,1095 @@ +""" +Support for installing and building the "wheel" binary package format. +""" +from __future__ import absolute_import + +import collections +import compileall +import csv +import hashlib +import logging +import os.path +import re +import shutil +import stat +import sys +import warnings +from base64 import urlsafe_b64encode +from email.parser import Parser + +from pip._vendor import pkg_resources +from pip._vendor.distlib.scripts import ScriptMaker +from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.six import StringIO + +from pip._internal import pep425tags +from pip._internal.download import path_to_url, unpack_url +from pip._internal.exceptions import ( + InstallationError, InvalidWheelFilename, UnsupportedWheel, +) +from pip._internal.locations import ( + PIP_DELETE_MARKER_FILENAME, distutils_scheme, +) +from pip._internal.models.link import Link +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import ( + call_subprocess, captured_stdout, ensure_dir, read_chunks, +) +from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM +from pip._internal.utils.temp_dir import TempDirectory +from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.utils.ui import open_spinner + +if MYPY_CHECK_RUNNING: + from typing import ( # noqa: F401 + Dict, List, Optional, Sequence, Mapping, Tuple, IO, Text, Any, + Union, Iterable + ) + from pip._vendor.packaging.requirements import Requirement # noqa: F401 + from pip._internal.req.req_install import InstallRequirement # noqa: F401 + from pip._internal.download import PipSession # noqa: F401 + from pip._internal.index import FormatControl, PackageFinder # noqa: F401 + from pip._internal.operations.prepare import ( # noqa: F401 + RequirementPreparer + ) + from pip._internal.cache import WheelCache # noqa: F401 + from pip._internal.pep425tags import Pep425Tag # noqa: F401 + + InstalledCSVRow = Tuple[str, ...] + + +VERSION_COMPATIBLE = (1, 0) + + +logger = logging.getLogger(__name__) + + +def normpath(src, p): + return os.path.relpath(src, p).replace(os.path.sep, '/') + + +def rehash(path, blocksize=1 << 20): + # type: (str, int) -> Tuple[str, str] + """Return (hash, length) for path using hashlib.sha256()""" + h = hashlib.sha256() + length = 0 + with open(path, 'rb') as f: + for block in read_chunks(f, size=blocksize): + length += len(block) + h.update(block) + digest = 'sha256=' + urlsafe_b64encode( + h.digest() + ).decode('latin1').rstrip('=') + # unicode/str python2 issues + return (digest, str(length)) # type: ignore + + +def open_for_csv(name, mode): + # type: (str, Text) -> IO + if sys.version_info[0] < 3: + nl = {} # type: Dict[str, Any] + bin = 'b' + else: + nl = {'newline': ''} # type: Dict[str, Any] + bin = '' + return open(name, mode + bin, **nl) + + +def replace_python_tag(wheelname, new_tag): + # type: (str, str) -> str + """Replace the Python tag in a wheel file name with a new value. + """ + parts = wheelname.split('-') + parts[-3] = new_tag + return '-'.join(parts) + + +def fix_script(path): + # type: (str) -> Optional[bool] + """Replace #!python with #!/path/to/python + Return True if file was changed.""" + # XXX RECORD hashes will need to be updated + if os.path.isfile(path): + with open(path, 'rb') as script: + firstline = script.readline() + if not firstline.startswith(b'#!python'): + return False + exename = sys.executable.encode(sys.getfilesystemencoding()) + firstline = b'#!' + exename + os.linesep.encode("ascii") + rest = script.read() + with open(path, 'wb') as script: + script.write(firstline) + script.write(rest) + return True + return None + + +dist_info_re = re.compile(r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>.+?))?) + \.dist-info$""", re.VERBOSE) + + +def root_is_purelib(name, wheeldir): + # type: (str, str) -> bool + """ + Return True if the extracted wheel in wheeldir should go into purelib. + """ + name_folded = name.replace("-", "_") + for item in os.listdir(wheeldir): + match = dist_info_re.match(item) + if match and match.group('name') == name_folded: + with open(os.path.join(wheeldir, item, 'WHEEL')) as wheel: + for line in wheel: + line = line.lower().rstrip() + if line == "root-is-purelib: true": + return True + return False + + +def get_entrypoints(filename): + # type: (str) -> Tuple[Dict[str, str], Dict[str, str]] + if not os.path.exists(filename): + return {}, {} + + # This is done because you can pass a string to entry_points wrappers which + # means that they may or may not be valid INI files. The attempt here is to + # strip leading and trailing whitespace in order to make them valid INI + # files. + with open(filename) as fp: + data = StringIO() + for line in fp: + data.write(line.strip()) + data.write("\n") + data.seek(0) + + # get the entry points and then the script names + entry_points = pkg_resources.EntryPoint.parse_map(data) + console = entry_points.get('console_scripts', {}) + gui = entry_points.get('gui_scripts', {}) + + def _split_ep(s): + """get the string representation of EntryPoint, remove space and split + on '='""" + return str(s).replace(" ", "").split("=") + + # convert the EntryPoint objects into strings with module:function + console = dict(_split_ep(v) for v in console.values()) + gui = dict(_split_ep(v) for v in gui.values()) + return console, gui + + +def message_about_scripts_not_on_PATH(scripts): + # type: (Sequence[str]) -> Optional[str] + """Determine if any scripts are not on PATH and format a warning. + + Returns a warning message if one or more scripts are not on PATH, + otherwise None. + """ + if not scripts: + return None + + # Group scripts by the path they were installed in + grouped_by_dir = collections.defaultdict(set) # type: Dict[str, set] + for destfile in scripts: + parent_dir = os.path.dirname(destfile) + script_name = os.path.basename(destfile) + grouped_by_dir[parent_dir].add(script_name) + + # We don't want to warn for directories that are on PATH. + not_warn_dirs = [ + os.path.normcase(i).rstrip(os.sep) for i in + os.environ.get("PATH", "").split(os.pathsep) + ] + # If an executable sits with sys.executable, we don't warn for it. + # This covers the case of venv invocations without activating the venv. + not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable))) + warn_for = { + parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items() + if os.path.normcase(parent_dir) not in not_warn_dirs + } + if not warn_for: + return None + + # Format a message + msg_lines = [] + for parent_dir, scripts in warn_for.items(): + scripts = sorted(scripts) + if len(scripts) == 1: + start_text = "script {} is".format(scripts[0]) + else: + start_text = "scripts {} are".format( + ", ".join(scripts[:-1]) + " and " + scripts[-1] + ) + + msg_lines.append( + "The {} installed in '{}' which is not on PATH." + .format(start_text, parent_dir) + ) + + last_line_fmt = ( + "Consider adding {} to PATH or, if you prefer " + "to suppress this warning, use --no-warn-script-location." + ) + if len(msg_lines) == 1: + msg_lines.append(last_line_fmt.format("this directory")) + else: + msg_lines.append(last_line_fmt.format("these directories")) + + # Returns the formatted multiline message + return "\n".join(msg_lines) + + +def sorted_outrows(outrows): + # type: (Iterable[InstalledCSVRow]) -> List[InstalledCSVRow] + """ + Return the given rows of a RECORD file in sorted order. + + Each row is a 3-tuple (path, hash, size) and corresponds to a record of + a RECORD file (see PEP 376 and PEP 427 for details). For the rows + passed to this function, the size can be an integer as an int or string, + or the empty string. + """ + # Normally, there should only be one row per path, in which case the + # second and third elements don't come into play when sorting. + # However, in cases in the wild where a path might happen to occur twice, + # we don't want the sort operation to trigger an error (but still want + # determinism). Since the third element can be an int or string, we + # coerce each element to a string to avoid a TypeError in this case. + # For additional background, see-- + # https://github.com/pypa/pip/issues/5868 + return sorted(outrows, key=lambda row: tuple(str(x) for x in row)) + + +def get_csv_rows_for_installed( + old_csv_rows, # type: Iterable[List[str]] + installed, # type: Dict[str, str] + changed, # type: set + generated, # type: List[str] + lib_dir, # type: str +): + # type: (...) -> List[InstalledCSVRow] + """ + :param installed: A map from archive RECORD path to installation RECORD + path. + """ + installed_rows = [] # type: List[InstalledCSVRow] + for row in old_csv_rows: + if len(row) > 3: + logger.warning( + 'RECORD line has more than three elements: {}'.format(row) + ) + # Make a copy because we are mutating the row. + row = list(row) + old_path = row[0] + new_path = installed.pop(old_path, old_path) + row[0] = new_path + if new_path in changed: + digest, length = rehash(new_path) + row[1] = digest + row[2] = length + installed_rows.append(tuple(row)) + for f in generated: + digest, length = rehash(f) + installed_rows.append((normpath(f, lib_dir), digest, str(length))) + for f in installed: + installed_rows.append((installed[f], '', '')) + return installed_rows + + +def move_wheel_files( + name, # type: str + req, # type: Requirement + wheeldir, # type: str + user=False, # type: bool + home=None, # type: Optional[str] + root=None, # type: Optional[str] + pycompile=True, # type: bool + scheme=None, # type: Optional[Mapping[str, str]] + isolated=False, # type: bool + prefix=None, # type: Optional[str] + warn_script_location=True # type: bool +): + # type: (...) -> None + """Install a wheel""" + # TODO: Investigate and break this up. + # TODO: Look into moving this into a dedicated class for representing an + # installation. + + if not scheme: + scheme = distutils_scheme( + name, user=user, home=home, root=root, isolated=isolated, + prefix=prefix, + ) + + if root_is_purelib(name, wheeldir): + lib_dir = scheme['purelib'] + else: + lib_dir = scheme['platlib'] + + info_dir = [] # type: List[str] + data_dirs = [] + source = wheeldir.rstrip(os.path.sep) + os.path.sep + + # Record details of the files moved + # installed = files copied from the wheel to the destination + # changed = files changed while installing (scripts #! line typically) + # generated = files newly generated during the install (script wrappers) + installed = {} # type: Dict[str, str] + changed = set() + generated = [] # type: List[str] + + # Compile all of the pyc files that we're going to be installing + if pycompile: + with captured_stdout() as stdout: + with warnings.catch_warnings(): + warnings.filterwarnings('ignore') + compileall.compile_dir(source, force=True, quiet=True) + logger.debug(stdout.getvalue()) + + def record_installed(srcfile, destfile, modified=False): + """Map archive RECORD paths to installation RECORD paths.""" + oldpath = normpath(srcfile, wheeldir) + newpath = normpath(destfile, lib_dir) + installed[oldpath] = newpath + if modified: + changed.add(destfile) + + def clobber(source, dest, is_base, fixer=None, filter=None): + ensure_dir(dest) # common for the 'include' path + + for dir, subdirs, files in os.walk(source): + basedir = dir[len(source):].lstrip(os.path.sep) + destdir = os.path.join(dest, basedir) + if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'): + continue + for s in subdirs: + destsubdir = os.path.join(dest, basedir, s) + if is_base and basedir == '' and destsubdir.endswith('.data'): + data_dirs.append(s) + continue + elif (is_base and + s.endswith('.dist-info') and + canonicalize_name(s).startswith( + canonicalize_name(req.name))): + assert not info_dir, ('Multiple .dist-info directories: ' + + destsubdir + ', ' + + ', '.join(info_dir)) + info_dir.append(destsubdir) + for f in files: + # Skip unwanted files + if filter and filter(f): + continue + srcfile = os.path.join(dir, f) + destfile = os.path.join(dest, basedir, f) + # directory creation is lazy and after the file filtering above + # to ensure we don't install empty dirs; empty dirs can't be + # uninstalled. + ensure_dir(destdir) + + # copyfile (called below) truncates the destination if it + # exists and then writes the new contents. This is fine in most + # cases, but can cause a segfault if pip has loaded a shared + # object (e.g. from pyopenssl through its vendored urllib3) + # Since the shared object is mmap'd an attempt to call a + # symbol in it will then cause a segfault. Unlinking the file + # allows writing of new contents while allowing the process to + # continue to use the old copy. + if os.path.exists(destfile): + os.unlink(destfile) + + # We use copyfile (not move, copy, or copy2) to be extra sure + # that we are not moving directories over (copyfile fails for + # directories) as well as to ensure that we are not copying + # over any metadata because we want more control over what + # metadata we actually copy over. + shutil.copyfile(srcfile, destfile) + + # Copy over the metadata for the file, currently this only + # includes the atime and mtime. + st = os.stat(srcfile) + if hasattr(os, "utime"): + os.utime(destfile, (st.st_atime, st.st_mtime)) + + # If our file is executable, then make our destination file + # executable. + if os.access(srcfile, os.X_OK): + st = os.stat(srcfile) + permissions = ( + st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + ) + os.chmod(destfile, permissions) + + changed = False + if fixer: + changed = fixer(destfile) + record_installed(srcfile, destfile, changed) + + clobber(source, lib_dir, True) + + assert info_dir, "%s .dist-info directory not found" % req + + # Get the defined entry points + ep_file = os.path.join(info_dir[0], 'entry_points.txt') + console, gui = get_entrypoints(ep_file) + + def is_entrypoint_wrapper(name): + # EP, EP.exe and EP-script.py are scripts generated for + # entry point EP by setuptools + if name.lower().endswith('.exe'): + matchname = name[:-4] + elif name.lower().endswith('-script.py'): + matchname = name[:-10] + elif name.lower().endswith(".pya"): + matchname = name[:-4] + else: + matchname = name + # Ignore setuptools-generated scripts + return (matchname in console or matchname in gui) + + for datadir in data_dirs: + fixer = None + filter = None + for subdir in os.listdir(os.path.join(wheeldir, datadir)): + fixer = None + if subdir == 'scripts': + fixer = fix_script + filter = is_entrypoint_wrapper + source = os.path.join(wheeldir, datadir, subdir) + dest = scheme[subdir] + clobber(source, dest, False, fixer=fixer, filter=filter) + + maker = ScriptMaker(None, scheme['scripts']) + + # Ensure old scripts are overwritten. + # See https://github.com/pypa/pip/issues/1800 + maker.clobber = True + + # Ensure we don't generate any variants for scripts because this is almost + # never what somebody wants. + # See https://bitbucket.org/pypa/distlib/issue/35/ + maker.variants = {''} + + # This is required because otherwise distlib creates scripts that are not + # executable. + # See https://bitbucket.org/pypa/distlib/issue/32/ + maker.set_mode = True + + # Simplify the script and fix the fact that the default script swallows + # every single stack trace. + # See https://bitbucket.org/pypa/distlib/issue/34/ + # See https://bitbucket.org/pypa/distlib/issue/33/ + def _get_script_text(entry): + if entry.suffix is None: + raise InstallationError( + "Invalid script entry point: %s for req: %s - A callable " + "suffix is required. Cf https://packaging.python.org/en/" + "latest/distributing.html#console-scripts for more " + "information." % (entry, req) + ) + return maker.script_template % { + "module": entry.prefix, + "import_name": entry.suffix.split(".")[0], + "func": entry.suffix, + } + # ignore type, because mypy disallows assigning to a method, + # see https://github.com/python/mypy/issues/2427 + maker._get_script_text = _get_script_text # type: ignore + maker.script_template = r"""# -*- coding: utf-8 -*- +import re +import sys + +from %(module)s import %(import_name)s + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(%(func)s()) +""" + + # Special case pip and setuptools to generate versioned wrappers + # + # The issue is that some projects (specifically, pip and setuptools) use + # code in setup.py to create "versioned" entry points - pip2.7 on Python + # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into + # the wheel metadata at build time, and so if the wheel is installed with + # a *different* version of Python the entry points will be wrong. The + # correct fix for this is to enhance the metadata to be able to describe + # such versioned entry points, but that won't happen till Metadata 2.0 is + # available. + # In the meantime, projects using versioned entry points will either have + # incorrect versioned entry points, or they will not be able to distribute + # "universal" wheels (i.e., they will need a wheel per Python version). + # + # Because setuptools and pip are bundled with _ensurepip and virtualenv, + # we need to use universal wheels. So, as a stopgap until Metadata 2.0, we + # override the versioned entry points in the wheel and generate the + # correct ones. This code is purely a short-term measure until Metadata 2.0 + # is available. + # + # To add the level of hack in this section of code, in order to support + # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment + # variable which will control which version scripts get installed. + # + # ENSUREPIP_OPTIONS=altinstall + # - Only pipX.Y and easy_install-X.Y will be generated and installed + # ENSUREPIP_OPTIONS=install + # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note + # that this option is technically if ENSUREPIP_OPTIONS is set and is + # not altinstall + # DEFAULT + # - The default behavior is to install pip, pipX, pipX.Y, easy_install + # and easy_install-X.Y. + pip_script = console.pop('pip', None) + if pip_script: + if "ENSUREPIP_OPTIONS" not in os.environ: + spec = 'pip = ' + pip_script + generated.extend(maker.make(spec)) + + if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": + spec = 'pip%s = %s' % (sys.version[:1], pip_script) + generated.extend(maker.make(spec)) + + spec = 'pip%s = %s' % (sys.version[:3], pip_script) + generated.extend(maker.make(spec)) + # Delete any other versioned pip entry points + 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) + if easy_install_script: + if "ENSUREPIP_OPTIONS" not in os.environ: + spec = 'easy_install = ' + easy_install_script + generated.extend(maker.make(spec)) + + spec = 'easy_install-%s = %s' % (sys.version[:3], easy_install_script) + generated.extend(maker.make(spec)) + # 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) + ] + for k in easy_install_ep: + del console[k] + + # Generate the console and GUI entry points specified in the wheel + if len(console) > 0: + generated_console_scripts = maker.make_multiple( + ['%s = %s' % kv for kv in console.items()] + ) + generated.extend(generated_console_scripts) + + if warn_script_location: + msg = message_about_scripts_not_on_PATH(generated_console_scripts) + if msg is not None: + logger.warning(msg) + + if len(gui) > 0: + generated.extend( + maker.make_multiple( + ['%s = %s' % kv for kv in gui.items()], + {'gui': True} + ) + ) + + # Record pip as the installer + installer = os.path.join(info_dir[0], 'INSTALLER') + temp_installer = os.path.join(info_dir[0], 'INSTALLER.pip') + with open(temp_installer, 'wb') as installer_file: + installer_file.write(b'pip\n') + shutil.move(temp_installer, installer) + generated.append(installer) + + # Record details of all files installed + record = os.path.join(info_dir[0], 'RECORD') + temp_record = os.path.join(info_dir[0], 'RECORD.pip') + with open_for_csv(record, 'r') as record_in: + with open_for_csv(temp_record, 'w+') as record_out: + reader = csv.reader(record_in) + outrows = get_csv_rows_for_installed( + reader, installed=installed, changed=changed, + generated=generated, lib_dir=lib_dir, + ) + writer = csv.writer(record_out) + # Sort to simplify testing. + for row in sorted_outrows(outrows): + writer.writerow(row) + shutil.move(temp_record, record) + + +def wheel_version(source_dir): + # type: (Optional[str]) -> Optional[Tuple[int, ...]] + """ + Return the Wheel-Version of an extracted wheel, if possible. + + Otherwise, return None if we couldn't parse / extract it. + """ + try: + dist = [d for d in pkg_resources.find_on_path(None, source_dir)][0] + + wheel_data = dist.get_metadata('WHEEL') + wheel_data = Parser().parsestr(wheel_data) + + version = wheel_data['Wheel-Version'].strip() + version = tuple(map(int, version.split('.'))) + return version + except Exception: + return None + + +def check_compatibility(version, name): + # type: (Optional[Tuple[int, ...]], str) -> None + """ + Raises errors or warns if called with an incompatible Wheel-Version. + + Pip should refuse to install a Wheel-Version that's a major series + ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when + installing a version only minor version ahead (e.g 1.2 > 1.1). + + version: a 2-tuple representing a Wheel-Version (Major, Minor) + name: name of wheel or package to raise exception about + + :raises UnsupportedWheel: when an incompatible Wheel-Version is given + """ + if not version: + raise UnsupportedWheel( + "%s is in an unsupported or invalid wheel" % name + ) + if version[0] > VERSION_COMPATIBLE[0]: + raise UnsupportedWheel( + "%s's Wheel-Version (%s) is not compatible with this version " + "of pip" % (name, '.'.join(map(str, version))) + ) + elif version > VERSION_COMPATIBLE: + logger.warning( + 'Installing from a newer Wheel-Version (%s)', + '.'.join(map(str, version)), + ) + + +class Wheel(object): + """A wheel file""" + + # TODO: Maybe move the class into the models sub-package + # TODO: Maybe move the install code into this class + + wheel_file_re = re.compile( + r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?)) + ((-(?P<build>\d[^-]*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?) + \.whl|\.dist-info)$""", + re.VERBOSE + ) + + def __init__(self, filename): + # type: (str) -> None + """ + :raises InvalidWheelFilename: when the filename is invalid for a wheel + """ + wheel_info = self.wheel_file_re.match(filename) + if not wheel_info: + raise InvalidWheelFilename( + "%s is not a valid wheel filename." % filename + ) + self.filename = filename + self.name = wheel_info.group('name').replace('_', '-') + # we'll assume "_" means "-" due to wheel naming scheme + # (https://github.com/pypa/pip/issues/1150) + self.version = wheel_info.group('ver').replace('_', '-') + self.build_tag = wheel_info.group('build') + self.pyversions = wheel_info.group('pyver').split('.') + self.abis = wheel_info.group('abi').split('.') + self.plats = wheel_info.group('plat').split('.') + + # All the tag combinations from this file + self.file_tags = { + (x, y, z) for x in self.pyversions + for y in self.abis for z in self.plats + } + + def support_index_min(self, tags=None): + # type: (Optional[List[Pep425Tag]]) -> Optional[int] + """ + Return the lowest index that one of the wheel's file_tag combinations + achieves in the supported_tags list e.g. if there are 8 supported tags, + and one of the file tags is first in the list, then return 0. Returns + None is the wheel is not supported. + """ + if tags is None: # for mock + tags = pep425tags.get_supported() + indexes = [tags.index(c) for c in self.file_tags if c in tags] + return min(indexes) if indexes else None + + def supported(self, tags=None): + # type: (Optional[List[Pep425Tag]]) -> bool + """Is this wheel supported on this system?""" + if tags is None: # for mock + tags = pep425tags.get_supported() + return bool(set(tags).intersection(self.file_tags)) + + +def _contains_egg_info( + s, _egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)): + """Determine whether the string looks like an egg_info. + + :param s: The string to parse. E.g. foo-2.1 + """ + return bool(_egg_info_re.search(s)) + + +def should_use_ephemeral_cache( + req, # type: InstallRequirement + format_control, # type: FormatControl + autobuilding, # type: bool + cache_available # type: bool +): + # type: (...) -> Optional[bool] + """ + Return whether to build an InstallRequirement object using the + ephemeral cache. + + :param cache_available: whether a cache directory is available for the + autobuilding=True case. + + :return: True or False to build the requirement with ephem_cache=True + or False, respectively; or None not to build the requirement. + """ + if req.constraint: + return None + if req.is_wheel: + if not autobuilding: + logger.info( + 'Skipping %s, due to already being wheel.', req.name, + ) + return None + if not autobuilding: + return False + + if req.editable or not req.source_dir: + return None + + if req.link and not req.link.is_artifact: + # VCS checkout. Build wheel just for this run. + return True + + if "binary" not in format_control.get_allowed_formats( + canonicalize_name(req.name)): + logger.info( + "Skipping bdist_wheel for %s, due to binaries " + "being disabled for it.", req.name, + ) + return None + + link = req.link + base, ext = link.splitext() + if cache_available and _contains_egg_info(base): + return False + + # Otherwise, build the wheel just for this run using the ephemeral + # cache since we are either in the case of e.g. a local directory, or + # no cache directory is available to use. + return True + + +def format_command( + command_args, # type: List[str] + command_output, # type: str +): + # type: (...) -> str + """ + Format command information for logging. + """ + text = 'Command arguments: {}\n'.format(command_args) + + if not command_output: + text += 'Command output: None' + elif logger.getEffectiveLevel() > logging.DEBUG: + text += 'Command output: [use --verbose to show]' + else: + if not command_output.endswith('\n'): + command_output += '\n' + text += ( + 'Command output:\n{}' + '-----------------------------------------' + ).format(command_output) + + return text + + +def get_legacy_build_wheel_path( + names, # type: List[str] + temp_dir, # type: str + req, # type: InstallRequirement + command_args, # type: List[str] + command_output, # type: str +): + # type: (...) -> Optional[str] + """ + Return the path to the wheel in the temporary build directory. + """ + # Sort for determinism. + names = sorted(names) + if not names: + msg = ( + 'Legacy build of wheel for {!r} created no files.\n' + ).format(req.name) + msg += format_command(command_args, command_output) + logger.warning(msg) + return None + + if len(names) > 1: + msg = ( + 'Legacy build of wheel for {!r} created more than one file.\n' + 'Filenames (choosing first): {}\n' + ).format(req.name, names) + msg += format_command(command_args, command_output) + logger.warning(msg) + + return os.path.join(temp_dir, names[0]) + + +class WheelBuilder(object): + """Build wheels from a RequirementSet.""" + + def __init__( + self, + finder, # type: PackageFinder + preparer, # type: RequirementPreparer + wheel_cache, # type: WheelCache + build_options=None, # type: Optional[List[str]] + global_options=None, # type: Optional[List[str]] + no_clean=False # type: bool + ): + # type: (...) -> None + self.finder = finder + self.preparer = preparer + self.wheel_cache = wheel_cache + + self._wheel_dir = preparer.wheel_download_dir + + self.build_options = build_options or [] + self.global_options = global_options or [] + self.no_clean = no_clean + + def _build_one(self, req, output_dir, python_tag=None): + """Build one wheel. + + :return: The filename of the built wheel, or None if the build failed. + """ + # Install build deps into temporary directory (PEP 518) + with req.build_env: + return self._build_one_inside_env(req, output_dir, + python_tag=python_tag) + + def _build_one_inside_env(self, req, output_dir, python_tag=None): + with TempDirectory(kind="wheel") as temp_dir: + if req.use_pep517: + builder = self._build_one_pep517 + else: + builder = self._build_one_legacy + wheel_path = builder(req, temp_dir.path, python_tag=python_tag) + if wheel_path is not None: + wheel_name = os.path.basename(wheel_path) + dest_path = os.path.join(output_dir, wheel_name) + try: + shutil.move(wheel_path, dest_path) + logger.info('Stored in directory: %s', output_dir) + return dest_path + except Exception: + pass + # Ignore return, we can't do anything else useful. + self._clean_one(req) + return None + + def _base_setup_args(self, req): + # NOTE: Eventually, we'd want to also -S to the flags here, when we're + # isolating. Currently, it breaks Python in virtualenvs, because it + # relies on site.py to find parts of the standard library outside the + # virtualenv. + return [ + sys.executable, '-u', '-c', + SETUPTOOLS_SHIM % req.setup_py + ] + list(self.global_options) + + def _build_one_pep517(self, req, tempd, python_tag=None): + """Build one InstallRequirement using the PEP 517 build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + assert req.metadata_directory is not None + try: + req.spin_message = 'Building wheel for %s (PEP 517)' % (req.name,) + logger.debug('Destination directory: %s', tempd) + wheel_name = req.pep517_backend.build_wheel( + tempd, + metadata_directory=req.metadata_directory + ) + if python_tag: + # General PEP 517 backends don't necessarily support + # a "--python-tag" option, so we rename the wheel + # file directly. + new_name = replace_python_tag(wheel_name, python_tag) + os.rename( + os.path.join(tempd, wheel_name), + os.path.join(tempd, new_name) + ) + # Reassign to simplify the return at the end of function + wheel_name = new_name + except Exception: + logger.error('Failed building wheel for %s', req.name) + return None + return os.path.join(tempd, wheel_name) + + def _build_one_legacy(self, req, tempd, python_tag=None): + """Build one InstallRequirement using the "legacy" build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + base_args = self._base_setup_args(req) + + spin_message = 'Building wheel for %s (setup.py)' % (req.name,) + with open_spinner(spin_message) as spinner: + logger.debug('Destination directory: %s', tempd) + wheel_args = base_args + ['bdist_wheel', '-d', tempd] \ + + self.build_options + + if python_tag is not None: + wheel_args += ["--python-tag", python_tag] + + try: + output = call_subprocess(wheel_args, cwd=req.setup_py_dir, + show_stdout=False, spinner=spinner) + except Exception: + spinner.finish("error") + logger.error('Failed building wheel for %s', req.name) + return None + names = os.listdir(tempd) + wheel_path = get_legacy_build_wheel_path( + names=names, + temp_dir=tempd, + req=req, + command_args=wheel_args, + command_output=output, + ) + return wheel_path + + def _clean_one(self, req): + base_args = self._base_setup_args(req) + + logger.info('Running setup.py clean for %s', req.name) + clean_args = base_args + ['clean', '--all'] + try: + call_subprocess(clean_args, cwd=req.source_dir, show_stdout=False) + return True + except Exception: + logger.error('Failed cleaning build dir for %s', req.name) + return False + + def build( + self, + requirements, # type: Iterable[InstallRequirement] + session, # type: PipSession + autobuilding=False # type: bool + ): + # type: (...) -> List[InstallRequirement] + """Build wheels. + + :param unpack: If True, replace the sdist we built from with the + newly built wheel, in preparation for installation. + :return: True if all the wheels built correctly. + """ + buildset = [] + format_control = self.finder.format_control + # Whether a cache directory is available for autobuilding=True. + cache_available = bool(self._wheel_dir or self.wheel_cache.cache_dir) + + for req in requirements: + ephem_cache = should_use_ephemeral_cache( + req, format_control=format_control, autobuilding=autobuilding, + cache_available=cache_available, + ) + if ephem_cache is None: + continue + + buildset.append((req, ephem_cache)) + + if not buildset: + return [] + + # Is any wheel build not using the ephemeral cache? + if any(not ephem_cache for _, ephem_cache in buildset): + have_directory_for_build = self._wheel_dir or ( + autobuilding and self.wheel_cache.cache_dir + ) + assert have_directory_for_build + + # TODO by @pradyunsg + # Should break up this method into 2 separate methods. + + # Build the wheels. + logger.info( + 'Building wheels for collected packages: %s', + ', '.join([req.name for (req, _) in buildset]), + ) + _cache = self.wheel_cache # shorter name + with indent_log(): + build_success, build_failure = [], [] + for req, ephem in buildset: + python_tag = None + if autobuilding: + python_tag = pep425tags.implementation_tag + if ephem: + output_dir = _cache.get_ephem_path_for_link(req.link) + else: + output_dir = _cache.get_path_for_link(req.link) + try: + ensure_dir(output_dir) + except OSError as e: + logger.warning("Building wheel for %s failed: %s", + req.name, e) + build_failure.append(req) + continue + else: + output_dir = self._wheel_dir + wheel_file = self._build_one( + req, output_dir, + python_tag=python_tag, + ) + if wheel_file: + build_success.append(req) + if autobuilding: + # XXX: This is mildly duplicative with prepare_files, + # but not close enough to pull out to a single common + # method. + # The code below assumes temporary source dirs - + # prevent it doing bad things. + if req.source_dir and not os.path.exists(os.path.join( + req.source_dir, PIP_DELETE_MARKER_FILENAME)): + raise AssertionError( + "bad source dir - missing marker") + # Delete the source we built the wheel from + req.remove_temporary_source() + # set the build directory again - name is known from + # the work prepare_files did. + req.source_dir = req.build_location( + self.preparer.build_dir + ) + # Update the link for this. + req.link = Link(path_to_url(wheel_file)) + assert req.link.is_wheel + # extract the wheel into the dir + unpack_url( + req.link, req.source_dir, None, False, + session=session, + ) + else: + build_failure.append(req) + + # notify success/failure + if build_success: + logger.info( + 'Successfully built %s', + ' '.join([req.name for req in build_success]), + ) + if build_failure: + logger.info( + 'Failed to build %s', + ' '.join([req.name for req in build_failure]), + ) + # Return a list of requirements that failed to build + return build_failure diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/__init__.py new file mode 100644 index 0000000..b919b54 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/__init__.py @@ -0,0 +1,111 @@ +""" +pip._vendor is for vendoring dependencies of pip to prevent needing pip to +depend on something external. + +Files inside of pip._vendor should be considered immutable and should only be +updated to versions from upstream. +""" +from __future__ import absolute_import + +import glob +import os.path +import sys + +# Downstream redistributors which have debundled our dependencies should also +# patch this value to be true. This will trigger the additional patching +# to cause things like "six" to be available as pip. +DEBUNDLED = False + +# By default, look in this directory for a bunch of .whl files which we will +# add to the beginning of sys.path before attempting to import anything. This +# is done to support downstream re-distributors like Debian and Fedora who +# wish to create their own Wheels for our dependencies to aid in debundling. +WHEEL_DIR = os.path.abspath(os.path.dirname(__file__)) + + +# Define a small helper function to alias our vendored modules to the real ones +# if the vendored ones do not exist. This idea of this was taken from +# https://github.com/kennethreitz/requests/pull/2567. +def vendored(modulename): + vendored_name = "{0}.{1}".format(__name__, modulename) + + try: + __import__(vendored_name, globals(), locals(), level=0) + except ImportError: + try: + __import__(modulename, globals(), locals(), level=0) + except ImportError: + # We can just silently allow import failures to pass here. If we + # got to this point it means that ``import pip._vendor.whatever`` + # failed and so did ``import whatever``. Since we're importing this + # upfront in an attempt to alias imports, not erroring here will + # just mean we get a regular import error whenever pip *actually* + # tries to import one of these modules to use it, which actually + # gives us a better error message than we would have otherwise + # gotten. + pass + else: + sys.modules[vendored_name] = sys.modules[modulename] + base, head = vendored_name.rsplit(".", 1) + setattr(sys.modules[base], head, sys.modules[modulename]) + + +# If we're operating in a debundled setup, then we want to go ahead and trigger +# the aliasing of our vendored libraries as well as looking for wheels to add +# to our sys.path. This will cause all of this code to be a no-op typically +# however downstream redistributors can enable it in a consistent way across +# all platforms. +if DEBUNDLED: + # Actually look inside of WHEEL_DIR to find .whl files and add them to the + # front of our sys.path. + sys.path[:] = glob.glob(os.path.join(WHEEL_DIR, "*.whl")) + sys.path + + # Actually alias all of our vendored dependencies. + vendored("cachecontrol") + vendored("colorama") + vendored("distlib") + vendored("distro") + vendored("html5lib") + vendored("lockfile") + vendored("six") + vendored("six.moves") + vendored("six.moves.urllib") + vendored("six.moves.urllib.parse") + vendored("packaging") + vendored("packaging.version") + vendored("packaging.specifiers") + vendored("pep517") + vendored("pkg_resources") + vendored("progress") + vendored("pytoml") + vendored("retrying") + vendored("requests") + vendored("requests.packages") + vendored("requests.packages.urllib3") + vendored("requests.packages.urllib3._collections") + vendored("requests.packages.urllib3.connection") + vendored("requests.packages.urllib3.connectionpool") + vendored("requests.packages.urllib3.contrib") + vendored("requests.packages.urllib3.contrib.ntlmpool") + vendored("requests.packages.urllib3.contrib.pyopenssl") + vendored("requests.packages.urllib3.exceptions") + vendored("requests.packages.urllib3.fields") + vendored("requests.packages.urllib3.filepost") + vendored("requests.packages.urllib3.packages") + vendored("requests.packages.urllib3.packages.ordered_dict") + vendored("requests.packages.urllib3.packages.six") + vendored("requests.packages.urllib3.packages.ssl_match_hostname") + vendored("requests.packages.urllib3.packages.ssl_match_hostname." + "_implementation") + vendored("requests.packages.urllib3.poolmanager") + vendored("requests.packages.urllib3.request") + vendored("requests.packages.urllib3.response") + vendored("requests.packages.urllib3.util") + vendored("requests.packages.urllib3.util.connection") + vendored("requests.packages.urllib3.util.request") + vendored("requests.packages.urllib3.util.response") + vendored("requests.packages.urllib3.util.retry") + vendored("requests.packages.urllib3.util.ssl_") + vendored("requests.packages.urllib3.util.timeout") + vendored("requests.packages.urllib3.util.url") + vendored("urllib3") diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/appdirs.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/appdirs.py new file mode 100644 index 0000000..2bd3911 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/appdirs.py @@ -0,0 +1,604 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See <http://github.com/ActiveState/appdirs> for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version_info__ = (1, 4, 3) +__version__ = '.'.join(map(str, __version_info__)) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/<AppName> + Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName> + Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName> + Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName> + Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName> + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/<AppName>". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/<AppName>', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/<AppName> + Unix: /usr/local/share/<AppName> or /usr/share/<AppName> + Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName> + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> + for a discussion of issues. + + Typical user config directories are: + Mac OS X: same as user_data_dir + Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/<AppName>". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system in ["win32", "darwin"]: + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/<AppName> + Unix: ~/.cache/<AppName> (XDG default) + Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache + Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state> + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/<AppName>". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be "<major>.<minor>". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/<AppName> + Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs + Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # <http://bugs.activestate.com/show_bug.cgi?id=85099>. + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # <http://bugs.activestate.com/show_bug.cgi?id=85099>. + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # <http://bugs.activestate.com/show_bug.cgi?id=85099>. + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/__init__.py new file mode 100644 index 0000000..8fdee66 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/__init__.py @@ -0,0 +1,11 @@ +"""CacheControl import Interface. + +Make it easy to import from cachecontrol without long namespaces. +""" +__author__ = "Eric Larson" +__email__ = "eric@ionrock.org" +__version__ = "0.12.5" + +from .wrapper import CacheControl +from .adapter import CacheControlAdapter +from .controller import CacheController diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/_cmd.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/_cmd.py new file mode 100644 index 0000000..f1e0ad9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/_cmd.py @@ -0,0 +1,57 @@ +import logging + +from pip._vendor import requests + +from pip._vendor.cachecontrol.adapter import CacheControlAdapter +from pip._vendor.cachecontrol.cache import DictCache +from pip._vendor.cachecontrol.controller import logger + +from argparse import ArgumentParser + + +def setup_logging(): + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler() + logger.addHandler(handler) + + +def get_session(): + adapter = CacheControlAdapter( + DictCache(), cache_etags=True, serializer=None, heuristic=None + ) + sess = requests.Session() + sess.mount("http://", adapter) + sess.mount("https://", adapter) + + sess.cache_controller = adapter.controller + return sess + + +def get_args(): + parser = ArgumentParser() + parser.add_argument("url", help="The URL to try and cache") + return parser.parse_args() + + +def main(args=None): + args = get_args() + sess = get_session() + + # Make a request to get a response + resp = sess.get(args.url) + + # Turn on logging + setup_logging() + + # try setting the cache + sess.cache_controller.cache_response(resp.request, resp.raw) + + # Now try to get it + if sess.cache_controller.cached_request(resp.request): + print("Cached!") + else: + print("Not cached :(") + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/adapter.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/adapter.py new file mode 100644 index 0000000..780eb28 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/adapter.py @@ -0,0 +1,133 @@ +import types +import functools +import zlib + +from pip._vendor.requests.adapters import HTTPAdapter + +from .controller import CacheController +from .cache import DictCache +from .filewrapper import CallbackFileWrapper + + +class CacheControlAdapter(HTTPAdapter): + invalidating_methods = {"PUT", "DELETE"} + + def __init__( + self, + cache=None, + cache_etags=True, + controller_class=None, + serializer=None, + heuristic=None, + cacheable_methods=None, + *args, + **kw + ): + super(CacheControlAdapter, self).__init__(*args, **kw) + self.cache = cache or DictCache() + self.heuristic = heuristic + self.cacheable_methods = cacheable_methods or ("GET",) + + controller_factory = controller_class or CacheController + self.controller = controller_factory( + self.cache, cache_etags=cache_etags, serializer=serializer + ) + + def send(self, request, cacheable_methods=None, **kw): + """ + Send a request. Use the request information to see if it + exists in the cache and cache the response if we need to and can. + """ + cacheable = cacheable_methods or self.cacheable_methods + if request.method in cacheable: + try: + cached_response = self.controller.cached_request(request) + except zlib.error: + cached_response = None + if cached_response: + return self.build_response(request, cached_response, from_cache=True) + + # check for etags and add headers if appropriate + request.headers.update(self.controller.conditional_headers(request)) + + resp = super(CacheControlAdapter, self).send(request, **kw) + + return resp + + def build_response( + self, request, response, from_cache=False, cacheable_methods=None + ): + """ + Build a response by making a request or using the cache. + + This will end up calling send and returning a potentially + cached response + """ + cacheable = cacheable_methods or self.cacheable_methods + if not from_cache and request.method in cacheable: + # Check for any heuristics that might update headers + # before trying to cache. + if self.heuristic: + response = self.heuristic.apply(response) + + # apply any expiration heuristics + if response.status == 304: + # We must have sent an ETag request. This could mean + # that we've been expired already or that we simply + # have an etag. In either case, we want to try and + # update the cache if that is the case. + cached_response = self.controller.update_cached_response( + request, response + ) + + if cached_response is not response: + from_cache = True + + # We are done with the server response, read a + # possible response body (compliant servers will + # not return one, but we cannot be 100% sure) and + # release the connection back to the pool. + response.read(decode_content=False) + response.release_conn() + + response = cached_response + + # We always cache the 301 responses + elif response.status == 301: + self.controller.cache_response(request, response) + else: + # Wrap the response file with a wrapper that will cache the + # response when the stream has been consumed. + response._fp = CallbackFileWrapper( + response._fp, + functools.partial( + self.controller.cache_response, request, response + ), + ) + if response.chunked: + super_update_chunk_length = response._update_chunk_length + + def _update_chunk_length(self): + super_update_chunk_length() + if self.chunk_left == 0: + self._fp._close() + + response._update_chunk_length = types.MethodType( + _update_chunk_length, response + ) + + resp = super(CacheControlAdapter, self).build_response(request, response) + + # See if we should invalidate the cache. + if request.method in self.invalidating_methods and resp.ok: + cache_url = self.controller.cache_url(request.url) + self.cache.delete(cache_url) + + # Give the request a from_cache attr to let people use it + resp.from_cache = from_cache + + return resp + + def close(self): + self.cache.close() + super(CacheControlAdapter, self).close() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/cache.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/cache.py new file mode 100644 index 0000000..94e0773 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/cache.py @@ -0,0 +1,39 @@ +""" +The cache object API for implementing caches. The default is a thread +safe in-memory dictionary. +""" +from threading import Lock + + +class BaseCache(object): + + def get(self, key): + raise NotImplementedError() + + def set(self, key, value): + raise NotImplementedError() + + def delete(self, key): + raise NotImplementedError() + + def close(self): + pass + + +class DictCache(BaseCache): + + def __init__(self, init_dict=None): + self.lock = Lock() + self.data = init_dict or {} + + def get(self, key): + return self.data.get(key, None) + + def set(self, key, value): + with self.lock: + self.data.update({key: value}) + + def delete(self, key): + with self.lock: + if key in self.data: + self.data.pop(key) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/__init__.py new file mode 100644 index 0000000..0e1658f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/__init__.py @@ -0,0 +1,2 @@ +from .file_cache import FileCache # noqa +from .redis_cache import RedisCache # noqa diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/file_cache.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/file_cache.py new file mode 100644 index 0000000..1ba0080 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/file_cache.py @@ -0,0 +1,146 @@ +import hashlib +import os +from textwrap import dedent + +from ..cache import BaseCache +from ..controller import CacheController + +try: + FileNotFoundError +except NameError: + # py2.X + FileNotFoundError = (IOError, OSError) + + +def _secure_open_write(filename, fmode): + # We only want to write to this file, so open it in write only mode + flags = os.O_WRONLY + + # os.O_CREAT | os.O_EXCL will fail if the file already exists, so we only + # will open *new* files. + # We specify this because we want to ensure that the mode we pass is the + # mode of the file. + flags |= os.O_CREAT | os.O_EXCL + + # Do not follow symlinks to prevent someone from making a symlink that + # we follow and insecurely open a cache file. + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + + # On Windows we'll mark this file as binary + if hasattr(os, "O_BINARY"): + flags |= os.O_BINARY + + # Before we open our file, we want to delete any existing file that is + # there + try: + os.remove(filename) + except (IOError, OSError): + # The file must not exist already, so we can just skip ahead to opening + pass + + # Open our file, the use of os.O_CREAT | os.O_EXCL will ensure that if a + # race condition happens between the os.remove and this line, that an + # error will be raised. Because we utilize a lockfile this should only + # happen if someone is attempting to attack us. + fd = os.open(filename, flags, fmode) + try: + return os.fdopen(fd, "wb") + + except: + # An error occurred wrapping our FD in a file object + os.close(fd) + raise + + +class FileCache(BaseCache): + + def __init__( + self, + directory, + forever=False, + filemode=0o0600, + dirmode=0o0700, + use_dir_lock=None, + lock_class=None, + ): + + if use_dir_lock is not None and lock_class is not None: + raise ValueError("Cannot use use_dir_lock and lock_class together") + + try: + from pip._vendor.lockfile import LockFile + from pip._vendor.lockfile.mkdirlockfile import MkdirLockFile + except ImportError: + notice = dedent( + """ + NOTE: In order to use the FileCache you must have + lockfile installed. You can install it via pip: + pip install lockfile + """ + ) + raise ImportError(notice) + + else: + if use_dir_lock: + lock_class = MkdirLockFile + + elif lock_class is None: + lock_class = LockFile + + self.directory = directory + self.forever = forever + self.filemode = filemode + self.dirmode = dirmode + self.lock_class = lock_class + + @staticmethod + def encode(x): + return hashlib.sha224(x.encode()).hexdigest() + + def _fn(self, name): + # NOTE: This method should not change as some may depend on it. + # See: https://github.com/ionrock/cachecontrol/issues/63 + hashed = self.encode(name) + parts = list(hashed[:5]) + [hashed] + return os.path.join(self.directory, *parts) + + def get(self, key): + name = self._fn(key) + try: + with open(name, "rb") as fh: + return fh.read() + + except FileNotFoundError: + return None + + def set(self, key, value): + name = self._fn(key) + + # Make sure the directory exists + try: + os.makedirs(os.path.dirname(name), self.dirmode) + except (IOError, OSError): + pass + + with self.lock_class(name) as lock: + # Write our actual file + with _secure_open_write(lock.path, self.filemode) as fh: + fh.write(value) + + def delete(self, key): + name = self._fn(key) + if not self.forever: + try: + os.remove(name) + except FileNotFoundError: + pass + + +def url_to_file_path(url, filecache): + """Return the file cache path based on the URL. + + This does not ensure the file exists! + """ + key = CacheController.cache_url(url) + return filecache._fn(key) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/redis_cache.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/redis_cache.py new file mode 100644 index 0000000..ed705ce --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/caches/redis_cache.py @@ -0,0 +1,33 @@ +from __future__ import division + +from datetime import datetime +from pip._vendor.cachecontrol.cache import BaseCache + + +class RedisCache(BaseCache): + + def __init__(self, conn): + self.conn = conn + + def get(self, key): + return self.conn.get(key) + + def set(self, key, value, expires=None): + if not expires: + self.conn.set(key, value) + else: + expires = expires - datetime.utcnow() + self.conn.setex(key, int(expires.total_seconds()), value) + + def delete(self, key): + self.conn.delete(key) + + def clear(self): + """Helper for clearing all the keys in a database. Use with + caution!""" + for key in self.conn.keys(): + self.conn.delete(key) + + def close(self): + """Redis uses connection pooling, no need to close the connection.""" + pass diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/compat.py new file mode 100644 index 0000000..33b5aed --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/compat.py @@ -0,0 +1,29 @@ +try: + from urllib.parse import urljoin +except ImportError: + from urlparse import urljoin + + +try: + import cPickle as pickle +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: + from pip._vendor.requests.packages.urllib3.response import HTTPResponse +except ImportError: + from pip._vendor.urllib3.response import HTTPResponse + +try: + from pip._vendor.requests.packages.urllib3.util import is_fp_closed +except ImportError: + from pip._vendor.urllib3.util import is_fp_closed + +# Replicate some six behaviour +try: + text_type = unicode +except NameError: + text_type = str diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/controller.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/controller.py new file mode 100644 index 0000000..1b2b943 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/controller.py @@ -0,0 +1,367 @@ +""" +The httplib2 algorithms ported for use with requests. +""" +import logging +import re +import calendar +import time +from email.utils import parsedate_tz + +from pip._vendor.requests.structures import CaseInsensitiveDict + +from .cache import DictCache +from .serialize import Serializer + + +logger = logging.getLogger(__name__) + +URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") + + +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) + """ + 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. + """ + + def __init__( + self, cache=None, cache_etags=True, serializer=None, status_codes=None + ): + self.cache = cache or DictCache() + self.cache_etags = cache_etags + self.serializer = serializer or Serializer() + self.cacheable_status_codes = status_codes or (200, 203, 300, 301) + + @classmethod + def _urlnorm(cls, uri): + """Normalize the URL to create a safe key for the cache""" + (scheme, authority, path, query, fragment) = parse_uri(uri) + if not scheme or not authority: + raise Exception("Only absolute URIs are allowed. uri = %s" % uri) + + scheme = scheme.lower() + authority = authority.lower() + + if not path: + path = "/" + + # Could do syntax based normalization of the URI before + # computing the digest. See Section 6.2.2 of Std 66. + request_uri = query and "?".join([path, query]) or path + defrag_uri = scheme + "://" + authority + request_uri + + return defrag_uri + + @classmethod + def cache_url(cls, uri): + return cls._urlnorm(uri) + + def parse_cache_control(self, headers): + known_directives = { + # https://tools.ietf.org/html/rfc7234#section-5.2 + "max-age": (int, True), + "max-stale": (int, False), + "min-fresh": (int, True), + "no-cache": (None, False), + "no-store": (None, False), + "no-transform": (None, False), + "only-if-cached": (None, False), + "must-revalidate": (None, False), + "public": (None, False), + "private": (None, False), + "proxy-revalidate": (None, False), + "s-maxage": (int, True), + } + + cc_headers = headers.get("cache-control", headers.get("Cache-Control", "")) + + retval = {} + + for cc_directive in cc_headers.split(","): + if not cc_directive.strip(): + continue + + parts = cc_directive.split("=", 1) + directive = parts[0].strip() + + try: + typ, required = known_directives[directive] + except KeyError: + logger.debug("Ignoring unknown cache-control directive: %s", directive) + continue + + if not typ or not required: + retval[directive] = None + if typ: + try: + retval[directive] = typ(parts[1].strip()) + except IndexError: + if required: + logger.debug( + "Missing value for cache-control " "directive: %s", + directive, + ) + except ValueError: + logger.debug( + "Invalid value for cache-control directive " "%s, must be %s", + directive, + typ.__name__, + ) + + return retval + + def cached_request(self, request): + """ + Return a cached response if it exists in the cache, otherwise + return False. + """ + cache_url = self.cache_url(request.url) + logger.debug('Looking up "%s" in the cache', cache_url) + cc = self.parse_cache_control(request.headers) + + # Bail out if the request insists on fresh data + if "no-cache" in cc: + logger.debug('Request header has "no-cache", cache bypassed') + return False + + if "max-age" in cc and cc["max-age"] == 0: + logger.debug('Request header has "max_age" as 0, cache bypassed') + return False + + # Request allows serving from the cache, let's see if we find something + cache_data = self.cache.get(cache_url) + if cache_data is None: + logger.debug("No cache entry available") + return False + + # Check whether it can be deserialized + resp = self.serializer.loads(request, cache_data) + 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 + # 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: + msg = ( + 'Returning cached "301 Moved Permanently" response ' + "(ignoring date and etag information)" + ) + logger.debug(msg) + return resp + + headers = CaseInsensitiveDict(resp.headers) + if not headers or "date" not in headers: + if "etag" not in headers: + # Without date or etag, the cached response can never be used + # and should be deleted. + logger.debug("Purging cached response: no date or etag") + self.cache.delete(cache_url) + logger.debug("Ignoring cached response: no date") + return False + + now = time.time() + date = calendar.timegm(parsedate_tz(headers["date"])) + current_age = max(0, now - date) + logger.debug("Current age based on date: %i", current_age) + + # TODO: There is an assumption that the result will be a + # urllib3 response object. This may not be best since we + # could probably avoid instantiating or constructing the + # response until we know we need it. + resp_cc = self.parse_cache_control(headers) + + # determine freshness + freshness_lifetime = 0 + + # Check the max-age pragma in the cache control header + if "max-age" in resp_cc: + freshness_lifetime = resp_cc["max-age"] + logger.debug("Freshness lifetime from max-age: %i", freshness_lifetime) + + # If there isn't a max-age, check for an expires header + elif "expires" in headers: + expires = parsedate_tz(headers["expires"]) + if expires is not None: + expire_time = calendar.timegm(expires) - date + freshness_lifetime = max(0, expire_time) + logger.debug("Freshness lifetime from expires: %i", freshness_lifetime) + + # Determine if we are setting freshness limit in the + # request. Note, this overrides what was in the response. + if "max-age" in cc: + freshness_lifetime = cc["max-age"] + logger.debug( + "Freshness lifetime from request max-age: %i", freshness_lifetime + ) + + if "min-fresh" in cc: + min_fresh = cc["min-fresh"] + # adjust our current age by our min fresh + current_age += min_fresh + logger.debug("Adjusted current age from min-fresh: %i", current_age) + + # Return entry if it is fresh enough + if freshness_lifetime > current_age: + logger.debug('The response is "fresh", returning cached response') + logger.debug("%i > %i", freshness_lifetime, current_age) + return resp + + # we're not fresh. If we don't have an Etag, clear it out + if "etag" not in headers: + logger.debug('The cached response is "stale" with no etag, purging') + self.cache.delete(cache_url) + + # return the original handler + return False + + def conditional_headers(self, request): + cache_url = self.cache_url(request.url) + resp = self.serializer.loads(request, self.cache.get(cache_url)) + new_headers = {} + + if resp: + headers = CaseInsensitiveDict(resp.headers) + + if "etag" in headers: + new_headers["If-None-Match"] = headers["ETag"] + + if "last-modified" in headers: + new_headers["If-Modified-Since"] = headers["Last-Modified"] + + return new_headers + + def cache_response(self, request, response, body=None, status_codes=None): + """ + Algorithm for caching requests. + + This assumes a requests Response object. + """ + # From httplib2: Don't cache 206's since we aren't going to + # handle byte range requests + cacheable_status_codes = status_codes or self.cacheable_status_codes + if response.status not in cacheable_status_codes: + logger.debug( + "Status code %s not in %s", response.status, cacheable_status_codes + ) + return + + response_headers = CaseInsensitiveDict(response.headers) + + # 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 + # skip trying to cache it. + if ( + body is not None + and "content-length" in response_headers + and response_headers["content-length"].isdigit() + and int(response_headers["content-length"]) != len(body) + ): + return + + cc_req = self.parse_cache_control(request.headers) + cc = self.parse_cache_control(response_headers) + + cache_url = self.cache_url(request.url) + logger.debug('Updating cache with response from "%s"', cache_url) + + # Delete it from the cache if we happen to have it stored there + no_store = False + if "no-store" in cc: + no_store = True + logger.debug('Response header has "no-store"') + if "no-store" in cc_req: + no_store = True + logger.debug('Request header has "no-store"') + if no_store and self.cache.get(cache_url): + logger.debug('Purging existing cache entry to honor "no-store"') + self.cache.delete(cache_url) + if no_store: + return + + # If we've been given an etag, then keep the response + if self.cache_etags and "etag" in response_headers: + logger.debug("Caching due to etag") + self.cache.set( + cache_url, self.serializer.dumps(request, response, body=body) + ) + + # 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 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: + # 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) + ) + + # 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) + ) + + def update_cached_response(self, request, response): + """On a 304 we will get a new set of headers that we want to + update our cached value with, assuming we have one. + + This should only ever be called when we've sent an ETag and + gotten a 304 as the response. + """ + cache_url = self.cache_url(request.url) + + cached_response = self.serializer.loads(request, self.cache.get(cache_url)) + + if not cached_response: + # we didn't have a cached response + return response + + # Lets update our headers with the headers from the new request: + # http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-4.1 + # + # The server isn't supposed to send headers that would make + # the cached body invalid. But... just in case, we'll be sure + # to strip out ones we know that might be problmatic due to + # typical assumptions. + excluded_headers = ["content-length"] + + cached_response.headers.update( + dict( + (k, v) + for k, v in response.headers.items() + if k.lower() not in excluded_headers + ) + ) + + # we want a 200 b/c we have content via the cache + cached_response.status = 200 + + # update our cache + self.cache.set(cache_url, self.serializer.dumps(request, cached_response)) + + return cached_response diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/filewrapper.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/filewrapper.py new file mode 100644 index 0000000..30ed4c5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/filewrapper.py @@ -0,0 +1,80 @@ +from io import BytesIO + + +class CallbackFileWrapper(object): + """ + Small wrapper around a fp object which will tee everything read into a + buffer, and when that file is closed it will execute a callback with the + contents of that buffer. + + All attributes are proxied to the underlying file object. + + This class uses members with a double underscore (__) leading prefix so as + not to accidentally shadow an attribute. + """ + + def __init__(self, fp, callback): + self.__buf = BytesIO() + self.__fp = fp + self.__callback = callback + + def __getattr__(self, name): + # The vaguaries of garbage collection means that self.__fp is + # not always set. By using __getattribute__ and the private + # name[0] allows looking up the attribute value and raising an + # AttributeError when it doesn't exist. This stop thigns from + # infinitely recursing calls to getattr in the case where + # self.__fp hasn't been set. + # + # [0] https://docs.python.org/2/reference/expressions.html#atom-identifiers + fp = self.__getattribute__("_CallbackFileWrapper__fp") + return getattr(fp, name) + + def __is_fp_closed(self): + try: + return self.__fp.fp is None + + except AttributeError: + pass + + try: + return self.__fp.closed + + except AttributeError: + pass + + # We just don't cache it then. + # TODO: Add some logging here... + return False + + def _close(self): + if self.__callback: + self.__callback(self.__buf.getvalue()) + + # We assign this to None here, because otherwise we can get into + # really tricky problems where the CPython interpreter dead locks + # because the callback is holding a reference to something which + # has a __del__ method. Setting this to None breaks the cycle + # and allows the garbage collector to do it's thing normally. + self.__callback = None + + def read(self, amt=None): + data = self.__fp.read(amt) + self.__buf.write(data) + if self.__is_fp_closed(): + self._close() + + return data + + def _safe_read(self, amt): + data = self.__fp._safe_read(amt) + if amt == 2 and data == b"\r\n": + # urllib executes this read to toss the CRLF at the end + # of the chunk. + return data + + self.__buf.write(data) + if self.__is_fp_closed(): + self._close() + + return data diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/heuristics.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/heuristics.py new file mode 100644 index 0000000..6c0e979 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/heuristics.py @@ -0,0 +1,135 @@ +import calendar +import time + +from email.utils import formatdate, parsedate, parsedate_tz + +from datetime import datetime, timedelta + +TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT" + + +def expire_after(delta, date=None): + date = date or datetime.utcnow() + return date + delta + + +def datetime_to_header(dt): + return formatdate(calendar.timegm(dt.timetuple())) + + +class BaseHeuristic(object): + + def warning(self, response): + """ + Return a valid 1xx warning header value describing the cache + adjustments. + + The response is provided too allow warnings like 113 + http://tools.ietf.org/html/rfc7234#section-5.5.4 where we need + to explicitly say response is over 24 hours old. + """ + return '110 - "Response is Stale"' + + def update_headers(self, response): + """Update the response headers with any new headers. + + NOTE: This SHOULD always include some Warning header to + signify that the response was cached by the client, not + by way of the provided headers. + """ + return {} + + def apply(self, response): + updated_headers = self.update_headers(response) + + if updated_headers: + response.headers.update(updated_headers) + warning_header_value = self.warning(response) + if warning_header_value is not None: + response.headers.update({"Warning": warning_header_value}) + + return response + + +class OneDayCache(BaseHeuristic): + """ + Cache the response by providing an expires 1 day in the + future. + """ + + def update_headers(self, response): + headers = {} + + if "expires" not in response.headers: + date = parsedate(response.headers["date"]) + expires = expire_after(timedelta(days=1), date=datetime(*date[:6])) + headers["expires"] = datetime_to_header(expires) + headers["cache-control"] = "public" + return headers + + +class ExpiresAfter(BaseHeuristic): + """ + Cache **all** requests for a defined time period. + """ + + def __init__(self, **kw): + self.delta = timedelta(**kw) + + def update_headers(self, response): + expires = expire_after(self.delta) + return {"expires": datetime_to_header(expires), "cache-control": "public"} + + def warning(self, response): + tmpl = "110 - Automatically cached for %s. Response might be stale" + return tmpl % self.delta + + +class LastModified(BaseHeuristic): + """ + If there is no Expires header already, fall back on Last-Modified + using the heuristic from + http://tools.ietf.org/html/rfc7234#section-4.2.2 + to calculate a reasonable value. + + Firefox also does something like this per + https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ + http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397 + Unlike mozilla we limit this to 24-hr. + """ + cacheable_by_default_statuses = { + 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501 + } + + def update_headers(self, resp): + headers = resp.headers + + if "expires" in headers: + return {} + + if "cache-control" in headers and headers["cache-control"] != "public": + return {} + + if resp.status not in self.cacheable_by_default_statuses: + return {} + + if "date" not in headers or "last-modified" not in headers: + return {} + + date = calendar.timegm(parsedate_tz(headers["date"])) + last_modified = parsedate(headers["last-modified"]) + if date is None or last_modified is None: + return {} + + now = time.time() + current_age = max(0, now - date) + delta = date - calendar.timegm(last_modified) + freshness_lifetime = max(0, min(delta / 10, 24 * 3600)) + if freshness_lifetime <= current_age: + return {} + + expires = date + freshness_lifetime + return {"expires": time.strftime(TIME_FMT, time.gmtime(expires))} + + def warning(self, resp): + return None diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/serialize.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/serialize.py new file mode 100644 index 0000000..ec43ff2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/serialize.py @@ -0,0 +1,186 @@ +import base64 +import io +import json +import zlib + +from pip._vendor import msgpack +from pip._vendor.requests.structures import CaseInsensitiveDict + +from .compat import HTTPResponse, pickle, text_type + + +def _b64_decode_bytes(b): + return base64.b64decode(b.encode("ascii")) + + +def _b64_decode_str(s): + return _b64_decode_bytes(s).decode("utf8") + + +class Serializer(object): + + def dumps(self, request, response, body=None): + response_headers = CaseInsensitiveDict(response.headers) + + if body is None: + 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 + # Python 2.x these objects are unicode and not str, even when + # they contain only ascii. The problem here is that msgpack + # understands the difference between unicode and bytes and we + # have it set to differentiate between them, however Python 2 + # doesn't know the difference. Forcing these to unicode will be + # enough to have msgpack know the difference. + data = { + u"response": { + u"body": body, + u"headers": dict( + (text_type(k), text_type(v)) for k, v in response.headers.items() + ), + u"status": response.status, + u"version": response.version, + u"reason": text_type(response.reason), + u"strict": response.strict, + u"decode_content": response.decode_content, + } + } + + # Construct our vary headers + data[u"vary"] = {} + if u"vary" in response_headers: + varied_headers = response_headers[u"vary"].split(",") + for header in varied_headers: + header = text_type(header).strip() + header_value = request.headers.get(header, None) + if header_value is not None: + header_value = text_type(header_value) + data[u"vary"][header] = header_value + + return b",".join([b"cc=4", msgpack.dumps(data, use_bin_type=True)]) + + def loads(self, request, data): + # Short circuit if we've been given an empty set of data + if not data: + return + + # Determine what version of the serializer the data was serialized + # with + try: + ver, data = data.split(b",", 1) + except ValueError: + ver = b"cc=0" + + # Make sure that our "ver" is actually a version and isn't a false + # positive from a , being in the data stream. + if ver[:3] != b"cc=": + data = ver + data + ver = b"cc=0" + + # Get the version number out of the cc=N + ver = ver.split(b"=", 1)[-1].decode("ascii") + + # Dispatch to the actual load method for the given version + try: + return getattr(self, "_loads_v{}".format(ver))(request, data) + + 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): + """Verify our vary headers match and construct a real urllib3 + HTTPResponse object. + """ + # Special case the '*' Vary value as it means we cannot actually + # determine if the cached response is suitable for this request. + if "*" in cached.get("vary", {}): + return + + # Ensure that the Vary headers for the cached response match our + # request + for header, value in cached.get("vary", {}).items(): + if request.headers.get(header, None) != value: + return + + body_raw = cached["response"].pop("body") + + headers = CaseInsensitiveDict(data=cached["response"]["headers"]) + if headers.get("transfer-encoding", "") == "chunked": + headers.pop("transfer-encoding") + + cached["response"]["headers"] = headers + + try: + body = io.BytesIO(body_raw) + 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 + # a Python 3 str (unicode string), which will cause the above to + # fail with: + # + # TypeError: 'str' does not support the buffer interface + body = io.BytesIO(body_raw.encode("utf8")) + + return HTTPResponse(body=body, preload_content=False, **cached["response"]) + + def _loads_v0(self, request, data): + # 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): + try: + cached = pickle.loads(data) + except ValueError: + return + + return self.prepare_response(request, cached) + + def _loads_v2(self, request, data): + try: + cached = json.loads(zlib.decompress(data).decode("utf8")) + except (ValueError, zlib.error): + return + + # We need to decode the items that we've base64 encoded + cached["response"]["body"] = _b64_decode_bytes(cached["response"]["body"]) + cached["response"]["headers"] = dict( + (_b64_decode_str(k), _b64_decode_str(v)) + for k, v in cached["response"]["headers"].items() + ) + cached["response"]["reason"] = _b64_decode_str(cached["response"]["reason"]) + cached["vary"] = dict( + (_b64_decode_str(k), _b64_decode_str(v) if v is not None else v) + for k, v in cached["vary"].items() + ) + + return self.prepare_response(request, cached) + + def _loads_v3(self, request, data): + # 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): + try: + cached = msgpack.loads(data, encoding="utf-8") + except ValueError: + return + + return self.prepare_response(request, cached) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/wrapper.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/wrapper.py new file mode 100644 index 0000000..265bfc8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/cachecontrol/wrapper.py @@ -0,0 +1,29 @@ +from .adapter import CacheControlAdapter +from .cache import DictCache + + +def CacheControl( + sess, + cache=None, + cache_etags=True, + serializer=None, + heuristic=None, + controller_class=None, + adapter_class=None, + cacheable_methods=None, +): + + cache = cache or DictCache() + adapter_class = adapter_class or CacheControlAdapter + adapter = adapter_class( + cache, + cache_etags=cache_etags, + serializer=serializer, + heuristic=heuristic, + controller_class=controller_class, + cacheable_methods=cacheable_methods, + ) + sess.mount("http://", adapter) + sess.mount("https://", adapter) + + return sess diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__init__.py new file mode 100644 index 0000000..ef71f3a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__init__.py @@ -0,0 +1,3 @@ +from .core import where + +__version__ = "2018.11.29" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__main__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__main__.py new file mode 100644 index 0000000..ae2aff5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/__main__.py @@ -0,0 +1,2 @@ +from pip._vendor.certifi import where +print(where()) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/cacert.pem b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/cacert.pem new file mode 100644 index 0000000..db68797 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/cacert.pem @@ -0,0 +1,4512 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +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=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----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" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=Class 2 Primary CA O=Certplus +# Subject: CN=Class 2 Primary CA O=Certplus +# Label: "Certplus Class 2 Primary CA" +# Serial: 177770208045934040241468760488327595043 +# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b +# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb +# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw +PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz +cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 +MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz +IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ +ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR +VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL +kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd +EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas +H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 +HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud +DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 +QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu +Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ +AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 +yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR +FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA +ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB +kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----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" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +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" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Label: "Deutsche Telekom Root CA 2" +# Serial: 38 +# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 +# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf +# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc +MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj +IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB +IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE +RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl +U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 +IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU +ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC +QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr +rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S +NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc +QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH +txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP +BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC +AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp +tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa +IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl +6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ +xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----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" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----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: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +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+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----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" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +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" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----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" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Label: "LuxTrust Global Root 2" +# Serial: 59914338225734147123941058376788110305822489521 +# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c +# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f +# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +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" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +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" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/core.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/core.py new file mode 100644 index 0000000..2d02ea4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/certifi/core.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem. +""" +import os + + +def where(): + f = os.path.dirname(__file__) + + return os.path.join(f, 'cacert.pem') + + +if __name__ == '__main__': + print(where()) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/__init__.py new file mode 100644 index 0000000..0f9f820 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/__init__.py @@ -0,0 +1,39 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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 .compat import PY2, PY3 +from .universaldetector import UniversalDetector +from .version import __version__, VERSION + + +def detect(byte_str): + """ + Detect the encoding of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError('Expected object of type bytes or bytearray, got: ' + '{0}'.format(type(byte_str))) + else: + byte_str = bytearray(byte_str) + detector = UniversalDetector() + detector.feed(byte_str) + return detector.close() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5freq.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5freq.py new file mode 100644 index 0000000..38f3251 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5freq.py @@ -0,0 +1,386 @@ +######################## 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 ######################### + +# Big5 frequency table +# by Taiwan's Mandarin Promotion Council +# <http://www.edu.tw:81/mandr/> +# +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +#Char to FreqOrder table +BIG5_TABLE_SIZE = 5376 + +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 +1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 + 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 +3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 +4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 +5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 + 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 + 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 + 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 +2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 +1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 +3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 + 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 +3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 +2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 + 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 +3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 +1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 +5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 + 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 +5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 +1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 + 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 + 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 +3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 +3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 + 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 +2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 +2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 + 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 + 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 +3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 +1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 +1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 +1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 +2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 + 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 +4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 +1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 +5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 +2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 + 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 + 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 + 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 + 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 +5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 + 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 +1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 + 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 + 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 +5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 +1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 + 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 +3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 +4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 +3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 + 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 + 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 +1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 +4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 +3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 +3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 +2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 +5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 +3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 +5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 +1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 +2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 +1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 + 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 +1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 +4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 +3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 + 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 + 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 + 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 +2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 +5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 +1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 +2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 +1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 +1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 +5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 +5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 +5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 +3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 +4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 +4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 +2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 +5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 +3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 + 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 +5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 +5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 +1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 +2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 +3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 +4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 +5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 +3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 +4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 +1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 +1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 +4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 +1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 + 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 +1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 +1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 +3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 + 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 +5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 +2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 +1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 +1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 +5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 + 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 +4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 + 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 +2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 + 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 +1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 +1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 + 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 +4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 +4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 +1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 +3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 +5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 +5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 +1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 +2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 +1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 +3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 +2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 +3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 +2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 +4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 +4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 +3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 + 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 +3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 + 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 +3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 +4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 +3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 +1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 +5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 + 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 +5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 +1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 + 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 +4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 +4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 + 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 +2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 +2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 +3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 +1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 +4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 +2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 +1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 +1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 +2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 +3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 +1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 +5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 +1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 +4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 +1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 + 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 +1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 +4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 +4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 +2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 +1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 +4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 + 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 +5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 +2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 +3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 +4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 + 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 +5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 +5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 +1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 +4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 +4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 +2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 +3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 +3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 +2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 +1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 +4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 +3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 +3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 +2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 +4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 +5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 +3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 +2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 +3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 +1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 +2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 +3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 +4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 +2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 +2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 +5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 +1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 +2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 +1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 +3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 +4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 +2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 +3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 +3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 +2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 +4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 +2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 +3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 +4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 +5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 +3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 + 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 +1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 +4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 +1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 +4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 +5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 + 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 +5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 +5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 +2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 +3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 +2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 +2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 + 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 +1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 +4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 +3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 +3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 + 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 +2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 + 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 +2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 +4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 +1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 +4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 +1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 +3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 + 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 +3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 +5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 +5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 +3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 +3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 +1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 +2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 +5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 +1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 +1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 +3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 + 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 +1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 +4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 +5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 +2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 +3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 + 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 +1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 +2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 +2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 +5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 +5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 +5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 +2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 +2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 +1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 +4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 +3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 +3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 +4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 +4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 +2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 +2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 +5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 +4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 +5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 +4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 + 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 + 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 +1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 +3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 +4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 +1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 +5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 +2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 +2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 +3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 +5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 +1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 +3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 +5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 +1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 +5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 +2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 +3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 +2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 +3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 +3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 +3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 +4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 + 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 +2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 +4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 +3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 +5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 +1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 +5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 + 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 +1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 + 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 +4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 +1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 +4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 +1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 + 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 +3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 +4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 +5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 + 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 +3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 + 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 +) + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5prober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5prober.py new file mode 100644 index 0000000..98f9970 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/big5prober.py @@ -0,0 +1,47 @@ +######################## 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import BIG5_SM_MODEL + + +class Big5Prober(MultiByteCharSetProber): + def __init__(self): + super(Big5Prober, self).__init__() + self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) + self.distribution_analyzer = Big5DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "Big5" + + @property + def language(self): + return "Chinese" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/chardistribution.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/chardistribution.py new file mode 100644 index 0000000..c0395f4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/chardistribution.py @@ -0,0 +1,233 @@ +######################## 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 ######################### + +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): + ENOUGH_DATA_THRESHOLD = 1024 + SURE_YES = 0.99 + SURE_NO = 0.01 + MINIMUM_DATA_THRESHOLD = 3 + + def __init__(self): + # 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 + # 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.reset() + + def reset(self): + """reset analyser, clear any state""" + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + self._total_chars = 0 # Total characters encountered + # The number of characters whose frequency order is less than 512 + self._freq_chars = 0 + + def feed(self, char, char_len): + """feed a character with known length""" + if char_len == 2: + # we only care about 2-bytes character in our distribution analysis + order = self.get_order(char) + else: + order = -1 + if order >= 0: + self._total_chars += 1 + # order is valid + if order < self._table_size: + if 512 > self._char_to_freq_order[order]: + self._freq_chars += 1 + + def get_confidence(self): + """return confidence based on existing data""" + # if we didn't receive any character in our consideration range, + # return negative answer + if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: + 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)) + 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): + # 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): + # 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 + # table. + return -1 + + +class EUCTWDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCTWDistributionAnalysis, self).__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): + # for euc-TW encoding, we are interested + # first byte range: 0xc4 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xC4: + return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 + else: + return -1 + + +class EUCKRDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCKRDistributionAnalysis, self).__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): + # for euc-KR encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xB0: + return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 + else: + return -1 + + +class GB2312DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(GB2312DistributionAnalysis, self).__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): + # for GB2312 encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + 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 + + +class Big5DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(Big5DistributionAnalysis, self).__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): + # for big5 encoding, we are interested + # first byte range: 0xa4 -- 0xfe + # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + 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 + + +class SJISDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(SJISDistributionAnalysis, self).__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): + # 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): + order = 188 * (first_char - 0x81) + elif (first_char >= 0xE0) and (first_char <= 0xEF): + order = 188 * (first_char - 0xE0 + 31) + else: + return -1 + order = order + second_char - 0x40 + if second_char > 0x7F: + order = -1 + return order + + +class EUCJPDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCJPDistributionAnalysis, self).__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): + # 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 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetgroupprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetgroupprober.py new file mode 100644 index 0000000..8b3738e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetgroupprober.py @@ -0,0 +1,106 @@ +######################## 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 ######################### + +from .enums import ProbingState +from .charsetprober import CharSetProber + + +class CharSetGroupProber(CharSetProber): + def __init__(self, lang_filter=None): + super(CharSetGroupProber, self).__init__(lang_filter=lang_filter) + self._active_num = 0 + self.probers = [] + self._best_guess_prober = None + + def reset(self): + super(CharSetGroupProber, self).reset() + self._active_num = 0 + for prober in self.probers: + if prober: + prober.reset() + prober.active = True + self._active_num += 1 + self._best_guess_prober = None + + @property + def charset_name(self): + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.charset_name + + @property + def language(self): + 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): + for prober in self.probers: + if not prober: + continue + if not prober.active: + continue + state = prober.feed(byte_str) + if not state: + continue + if state == ProbingState.FOUND_IT: + self._best_guess_prober = prober + return self.state + elif state == ProbingState.NOT_ME: + prober.active = False + self._active_num -= 1 + if self._active_num <= 0: + self._state = ProbingState.NOT_ME + return self.state + return self.state + + def get_confidence(self): + state = self.state + if state == ProbingState.FOUND_IT: + return 0.99 + elif 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) + continue + conf = prober.get_confidence() + 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 + if not self._best_guess_prober: + return 0.0 + return best_conf diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetprober.py new file mode 100644 index 0000000..eac4e59 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/charsetprober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 ######################### + +import logging +import re + +from .enums import ProbingState + + +class CharSetProber(object): + + SHORTCUT_THRESHOLD = 0.95 + + def __init__(self, lang_filter=None): + self._state = None + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + + def reset(self): + self._state = ProbingState.DETECTING + + @property + def charset_name(self): + return None + + def feed(self, buf): + pass + + @property + def state(self): + return self._state + + def get_confidence(self): + return 0.0 + + @staticmethod + def filter_high_byte_only(buf): + buf = re.sub(b'([\x00-\x7F])+', b' ', buf) + return buf + + @staticmethod + def filter_international_words(buf): + """ + 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() + + # 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) + + for word in words: + filtered.extend(word[:-1]) + + # If the last character in the word is a marker, replace it with a + # space as markers shouldn't affect our analysis (they are used + # 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' ' + filtered.extend(last_char) + + return filtered + + @staticmethod + def filter_with_english_letters(buf): + """ + 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``. + """ + filtered = bytearray() + in_tag = False + prev = 0 + + 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 + + # 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 + 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 + + # If we're not in a tag... + if not in_tag: + # Keep everything after last non-extended-ASCII, non-alphabetic + # character + filtered.extend(buf[prev:]) + + return filtered diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/__init__.py @@ -0,0 +1 @@ + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/chardetect.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/chardetect.py new file mode 100644 index 0000000..c61136b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cli/chardetect.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +""" +Script which takes one or more file paths and reports on their detected +encodings + +Example:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +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 pip._vendor.chardet import __version__ +from pip._vendor.chardet.compat import PY2 +from pip._vendor.chardet.universaldetector import UniversalDetector + + +def description_of(lines, name='stdin'): + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + """ + u = UniversalDetector() + for line in lines: + line = bytearray(line) + u.feed(line) + # shortcut out of the loop to save reading further - particularly useful if we read a BOM. + if u.done: + break + u.close() + result = u.result + if PY2: + name = name.decode(sys.getfilesystemencoding(), 'ignore') + if result['encoding']: + return '{0}: {1} with confidence {2}'.format(name, result['encoding'], + result['confidence']) + else: + return '{0}: no result'.format(name) + + +def main(argv=None): + """ + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + """ + # 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 {0}'.format(__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__': + main() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/codingstatemachine.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/codingstatemachine.py new file mode 100644 index 0000000..68fba44 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/codingstatemachine.py @@ -0,0 +1,88 @@ +######################## 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): +# 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 ######################### + +import logging + +from .enums import MachineState + + +class CodingStateMachine(object): + """ + 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 + state machine available, one byte at a time. The state machine changes its + state based on its previous state and the byte it receives. There are 3 + states in a state machine that are of interest to an auto-detector: + + START state: This is the state to start with, or a legal byte sequence + (i.e. a valid code point) for character has been identified. + + ME state: This indicates that the state machine identified a byte sequence + that is specific to the charset it is designed for and that + there is no other possible encoding which can contain this byte + sequence. This will to lead to an immediate positive answer for + the detector. + + ERROR state: This indicates the state machine identified an illegal byte + sequence for that encoding. This will lead to an immediate + negative answer for this encoding. Detector will exclude this + encoding from consideration from here on. + """ + def __init__(self, sm): + self._model = sm + self._curr_byte_pos = 0 + self._curr_char_len = 0 + self._curr_state = None + self.logger = logging.getLogger(__name__) + self.reset() + + def reset(self): + self._curr_state = MachineState.START + + def next_state(self, c): + # for each byte we get its class + # if it is first byte, we also get byte length + 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] + # 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] + self._curr_byte_pos += 1 + return self._curr_state + + def get_current_charlen(self): + return self._curr_char_len + + def get_coding_state_machine(self): + return self._model['name'] + + @property + def language(self): + return self._model['language'] diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/compat.py new file mode 100644 index 0000000..ddd7468 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/compat.py @@ -0,0 +1,34 @@ +######################## BEGIN LICENSE BLOCK ######################## +# Contributor(s): +# Dan Blanchard +# Ian Cordasco +# +# 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 ######################### + +import sys + + +if sys.version_info < (3, 0): + PY2 = True + PY3 = False + base_str = (str, unicode) + text_type = unicode +else: + PY2 = False + PY3 = True + base_str = (bytes, str) + text_type = str diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cp949prober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cp949prober.py new file mode 100644 index 0000000..efd793a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/cp949prober.py @@ -0,0 +1,49 @@ +######################## 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): +# 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 ######################### + +from .chardistribution import EUCKRDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import CP949_SM_MODEL + + +class CP949Prober(MultiByteCharSetProber): + def __init__(self): + super(CP949Prober, self).__init__() + self.coding_sm = CodingStateMachine(CP949_SM_MODEL) + # NOTE: CP949 is a superset of EUC-KR, so the distribution should be + # not different. + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "CP949" + + @property + def language(self): + return "Korean" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/enums.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/enums.py new file mode 100644 index 0000000..0451207 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/enums.py @@ -0,0 +1,76 @@ +""" +All of the Enums that are used throughout the chardet package. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + + +class InputState(object): + """ + This enum represents the different states a universal detector can be in. + """ + PURE_ASCII = 0 + ESC_ASCII = 1 + HIGH_BYTE = 2 + + +class LanguageFilter(object): + """ + This enum represents the different language filters we can apply to a + ``UniversalDetector``. + """ + CHINESE_SIMPLIFIED = 0x01 + CHINESE_TRADITIONAL = 0x02 + JAPANESE = 0x04 + KOREAN = 0x08 + NON_CJK = 0x10 + ALL = 0x1F + CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL + CJK = CHINESE | JAPANESE | KOREAN + + +class ProbingState(object): + """ + This enum represents the different states a prober can be in. + """ + DETECTING = 0 + FOUND_IT = 1 + NOT_ME = 2 + + +class MachineState(object): + """ + This enum represents the different states a state machine can be in. + """ + START = 0 + ERROR = 1 + ITS_ME = 2 + + +class SequenceLikelihood(object): + """ + 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): + """:returns: The number of likelihood categories in the enum.""" + return 4 + + +class CharacterCategory(object): + """ + 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 + DIGIT = 252 + CONTROL = 251 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escprober.py new file mode 100644 index 0000000..c70493f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escprober.py @@ -0,0 +1,101 @@ +######################## 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): +# 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 ######################### + +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) + + +class EscCharSetProber(CharSetProber): + """ + This CharSetProber uses a "code scheme" approach for detecting encodings, + whereby easily recognizable escape or shift sequences are relied on to + identify these encodings. + """ + + def __init__(self, lang_filter=None): + super(EscCharSetProber, self).__init__(lang_filter=lang_filter) + self.coding_sm = [] + if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: + self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) + self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) + if self.lang_filter & LanguageFilter.JAPANESE: + 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.reset() + + def reset(self): + super(EscCharSetProber, self).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) + self._detected_charset = None + self._detected_language = None + + @property + def charset_name(self): + return self._detected_charset + + @property + def language(self): + return self._detected_language + + def get_confidence(self): + if self._detected_charset: + return 0.99 + else: + return 0.00 + + def feed(self, byte_str): + for c in byte_str: + for coding_sm in self.coding_sm: + if not coding_sm or not coding_sm.active: + continue + coding_state = coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + coding_sm.active = False + self.active_sm_count -= 1 + if self.active_sm_count <= 0: + self._state = ProbingState.NOT_ME + return self.state + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + self._detected_charset = coding_sm.get_coding_state_machine() + self._detected_language = coding_sm.language + return self.state + + return self.state diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escsm.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escsm.py new file mode 100644 index 0000000..0069523 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/escsm.py @@ -0,0 +1,246 @@ +######################## 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): +# 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 ######################### + +from .enums import MachineState + +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 +) + +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 +) + +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'} + +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 +) + +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 +) + +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'} + +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 +) + +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 +) + +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'} + +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 +) + +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 +) + +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'} + + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/eucjpprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/eucjpprober.py new file mode 100644 index 0000000..20ce8f7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/eucjpprober.py @@ -0,0 +1,92 @@ +######################## 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): +# 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 ######################### + +from .enums import ProbingState, MachineState +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCJPDistributionAnalysis +from .jpcntx import EUCJPContextAnalysis +from .mbcssm import EUCJP_SM_MODEL + + +class EUCJPProber(MultiByteCharSetProber): + def __init__(self): + super(EUCJPProber, self).__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() + self.context_analyzer.reset() + + @property + def charset_name(self): + return "EUC-JP" + + @property + def language(self): + 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]) + if coding_state == MachineState.ERROR: + 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: + self._state = ProbingState.FOUND_IT + break + elif 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, 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._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)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrfreq.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrfreq.py new file mode 100644 index 0000000..b68078c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrfreq.py @@ -0,0 +1,195 @@ +######################## 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 ######################### + +# Sampling from about 20M text materials include literature and computer technology + +# 128 --> 0.79 +# 256 --> 0.92 +# 512 --> 0.986 +# 1024 --> 0.99944 +# 2048 --> 0.99999 +# +# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 +# Random Distribution Ration = 512 / (2350-512) = 0.279. +# +# Typical Distribution Ratio + +EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 + +EUCKR_TABLE_SIZE = 2352 + +# Char to FreqOrder table , +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, +1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, + 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, + 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, + 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, +1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, + 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, + 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, +1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, +1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, +1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, +1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, +1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, + 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, +1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, +1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, +1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, +1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, + 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, +1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, + 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, + 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, +1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, + 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, +1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, + 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, + 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, +1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, +1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, +1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, +1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, + 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, +1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, + 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, + 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, +1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, +1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, +1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, +1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, +1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, +1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, + 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, + 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, + 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, +1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, + 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, +1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, + 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, + 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, +2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, + 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, + 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, +2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, +2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, +2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, + 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, + 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, +2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, + 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, +1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, +2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, +1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, +2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, +2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, +1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, + 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, +2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, +2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, + 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, + 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, +2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, +1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, +2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, +2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, +2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, +2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, +2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, +2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, +1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, +2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, +2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, +2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, +2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, +2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, +1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, +1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, +2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, +1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, +2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, +1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, + 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, +2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, + 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, +2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, + 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, +2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, +2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, + 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, +2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, +1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, + 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, +1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, +2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, +1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, +2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, + 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, +2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, +1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, +2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, +1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, +2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, +1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, + 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, +2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, +2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, + 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, + 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, +1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, +1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, + 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, +2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, +2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, + 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, + 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, + 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, +2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, + 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, + 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, +2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, +2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, + 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, +2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, +1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, + 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, +2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, +2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, +2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, + 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, + 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, + 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, +2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, +2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, +2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, +1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, +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 +) + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrprober.py new file mode 100644 index 0000000..345a060 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euckrprober.py @@ -0,0 +1,47 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCKRDistributionAnalysis +from .mbcssm import EUCKR_SM_MODEL + + +class EUCKRProber(MultiByteCharSetProber): + def __init__(self): + super(EUCKRProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-KR" + + @property + def language(self): + return "Korean" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwfreq.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwfreq.py new file mode 100644 index 0000000..ed7a995 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwfreq.py @@ -0,0 +1,387 @@ +######################## 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 ######################### + +# EUCTW frequency table +# Converted from big5 work +# by Taiwan's Mandarin Promotion Council +# <http:#www.edu.tw:81/mandr/> + +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table , +EUCTW_TABLE_SIZE = 5376 + +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 +) + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwprober.py new file mode 100644 index 0000000..35669cc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/euctwprober.py @@ -0,0 +1,46 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCTWDistributionAnalysis +from .mbcssm import EUCTW_SM_MODEL + +class EUCTWProber(MultiByteCharSetProber): + def __init__(self): + super(EUCTWProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) + self.distribution_analyzer = EUCTWDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-TW" + + @property + def language(self): + return "Taiwan" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312freq.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312freq.py new file mode 100644 index 0000000..697837b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312freq.py @@ -0,0 +1,283 @@ +######################## 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 ######################### + +# GB2312 most frequently used character table +# +# Char to FreqOrder table , from hz6763 + +# 512 --> 0.79 -- 0.79 +# 1024 --> 0.92 -- 0.13 +# 2048 --> 0.98 -- 0.06 +# 6768 --> 1.00 -- 0.02 +# +# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 +# Random Distribution Ration = 512 / (3755 - 512) = 0.157 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR + +GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 + +GB2312_TABLE_SIZE = 3760 + +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, +2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, + 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, +1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, +1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, + 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, +1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, +2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, +3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, + 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, +1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, + 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, +2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, + 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, +2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, +1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, +3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, + 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, +1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, + 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, +2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, +1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, +3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, +1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, +2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, +1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, + 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, +3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, +3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, + 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, +3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, + 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, +1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, +3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, +2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, +1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, + 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, +1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, +4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, + 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, +3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, +3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, + 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, +1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, +2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, +1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, +1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, + 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, +3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, +3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, +4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, + 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, +3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, +1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, +1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, +4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, + 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, + 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, +3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, +1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, + 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, +1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, +2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, + 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, + 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, + 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, +3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, +4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, +3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, + 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, +2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, +2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, +2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, + 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, +2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, + 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, + 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, + 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, +3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, +2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, +2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, +1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, + 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, +2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, + 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, + 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, +1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, +1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, + 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, + 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, +1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, +2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, +3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, +2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, +2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, +2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, +3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, +1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, +1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, +2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, +1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, +3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, +1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, +1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, +3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, + 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, +2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, +1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, +4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, +1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, +1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, +3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, +1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, + 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, + 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, +1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, + 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, +1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, +1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, + 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, +3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, +4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, +3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, +2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, +2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, +1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, +3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, +2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, +1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, +1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, + 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, +2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, +2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, +3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, +4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, +3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, + 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, +3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, +2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, +1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, + 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, + 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, +3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, +4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, +2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, +1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, +1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, + 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, +1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, +3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, + 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, + 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, +1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, + 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, +1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, + 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, +2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, + 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, +2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, +2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, +1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, +1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, +2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, + 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, +1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, +1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, +2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, +2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, +3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, +1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, +4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, + 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, + 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, +3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, +1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, + 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, +3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, +1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, +4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, +1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, +2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, +1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, + 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, +1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, +3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, + 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, +2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, + 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, +1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, +1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, +1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, +3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, +2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, +3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, +3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, +3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, + 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, +2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, + 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, +2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, + 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, +1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, + 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, + 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, +1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, +3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, +3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, +1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, +1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, +3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, +2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, +2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, +1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, +3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, + 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, +4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, +1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, +2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, +3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, +3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, +1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, + 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, + 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, +2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, + 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, +1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, + 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, +1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, +1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, +1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, +1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, +1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, + 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 +) + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312prober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312prober.py new file mode 100644 index 0000000..8446d2d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/gb2312prober.py @@ -0,0 +1,46 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import GB2312DistributionAnalysis +from .mbcssm import GB2312_SM_MODEL + +class GB2312Prober(MultiByteCharSetProber): + def __init__(self): + super(GB2312Prober, self).__init__() + self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) + self.distribution_analyzer = GB2312DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "GB2312" + + @property + def language(self): + return "Chinese" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/hebrewprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/hebrewprober.py new file mode 100644 index 0000000..b0e1bf4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/hebrewprober.py @@ -0,0 +1,292 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Shy Shalom +# Portions created by the Initial Developer are Copyright (C) 2005 +# 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 ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState + +# This prober doesn't actually recognize a language or a charset. +# It is a helper prober for the use of the Hebrew model probers + +### General ideas of the Hebrew charset recognition ### +# +# Four main charsets exist in Hebrew: +# "ISO-8859-8" - Visual Hebrew +# "windows-1255" - Logical Hebrew +# "ISO-8859-8-I" - Logical Hebrew +# "x-mac-hebrew" - ?? Logical Hebrew ?? +# +# Both "ISO" charsets use a completely identical set of code points, whereas +# "windows-1255" and "x-mac-hebrew" are two different proper supersets of +# these code points. windows-1255 defines additional characters in the range +# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific +# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. +# x-mac-hebrew defines similar additional code points but with a different +# mapping. +# +# As far as an average Hebrew text with no diacritics is concerned, all four +# charsets are identical with respect to code points. Meaning that for the +# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters +# (including final letters). +# +# The dominant difference between these charsets is their directionality. +# "Visual" directionality means that the text is ordered as if the renderer is +# not aware of a BIDI rendering algorithm. The renderer sees the text and +# draws it from left to right. The text itself when ordered naturally is read +# backwards. A buffer of Visual Hebrew generally looks like so: +# "[last word of first line spelled backwards] [whole line ordered backwards +# and spelled backwards] [first word of first line spelled backwards] +# [end of line] [last word of second line] ... etc' " +# adding punctuation marks, numbers and English text to visual text is +# naturally also "visual" and from left to right. +# +# "Logical" directionality means the text is ordered "naturally" according to +# the order it is read. It is the responsibility of the renderer to display +# the text from right to left. A BIDI algorithm is used to place general +# punctuation marks, numbers and English text in the text. +# +# Texts in x-mac-hebrew are almost impossible to find on the Internet. From +# what little evidence I could find, it seems that its general directionality +# is Logical. +# +# To sum up all of the above, the Hebrew probing mechanism knows about two +# charsets: +# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are +# backwards while line order is natural. For charset recognition purposes +# the line order is unimportant (In fact, for this implementation, even +# word order is unimportant). +# Logical Hebrew - "windows-1255" - normal, naturally ordered text. +# +# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be +# specifically identified. +# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew +# that contain special punctuation marks or diacritics is displayed with +# some unconverted characters showing as question marks. This problem might +# be corrected using another model prober for x-mac-hebrew. Due to the fact +# that x-mac-hebrew texts are so rare, writing another model prober isn't +# worth the effort and performance hit. +# +#### The Prober #### +# +# The prober is divided between two SBCharSetProbers and a HebrewProber, +# all of which are managed, created, fed data, inquired and deleted by the +# SBCSGroupProber. The two SBCharSetProbers identify that the text is in +# fact some kind of Hebrew, Logical or Visual. The final decision about which +# one is it is made by the HebrewProber by combining final-letter scores +# with the scores of the two SBCharSetProbers to produce a final answer. +# +# The SBCSGroupProber is responsible for stripping the original text of HTML +# tags, English characters, numbers, low-ASCII punctuation characters, spaces +# and new lines. It reduces any sequence of such characters to a single space. +# The buffer fed to each prober in the SBCS group prober is pure text in +# high-ASCII. +# The two SBCharSetProbers (model probers) share the same language model: +# Win1255Model. +# The first SBCharSetProber uses the model normally as any other +# SBCharSetProber does, to recognize windows-1255, upon which this model was +# built. The second SBCharSetProber is told to make the pair-of-letter +# lookup in the language model backwards. This in practice exactly simulates +# a visual Hebrew model using the windows-1255 logical Hebrew model. +# +# The HebrewProber is not using any language model. All it does is look for +# final-letter evidence suggesting the text is either logical Hebrew or visual +# Hebrew. Disjointed from the model probers, the results of the HebrewProber +# alone are meaningless. HebrewProber always returns 0.00 as confidence +# since it never identifies a charset by itself. Instead, the pointer to the +# HebrewProber is passed to the model probers as a helper "Name Prober". +# When the Group prober receives a positive identification from any prober, +# it asks for the name of the charset identified. If the prober queried is a +# Hebrew model prober, the model prober forwards the call to the +# HebrewProber to make the final decision. In the HebrewProber, the +# decision is made according to the final-letters scores maintained and Both +# 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): + # 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 + + # Minimum Visual vs Logical final letter score difference. + # If the difference is below this, don't rely solely on the final letter score + # distance. + MIN_FINAL_CHAR_DISTANCE = 5 + + # Minimum Visual vs Logical model score difference. + # If the difference is below this, don't rely at all on the model score + # distance. + MIN_MODEL_DISTANCE = 0.01 + + 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 + self.reset() + + def reset(self): + 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 = ' ' + # These probers are owned by the group prober. + + def set_model_probers(self, logicalProber, visualProber): + self._logical_prober = logicalProber + self._visual_prober = visualProber + + 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_non_final(self, c): + # 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 + # causing the Non-Final tsadi to appear at an end of a word even + # though this is not the case in the original text. + # The letters Pe and Kaf rarely display a related behavior of not being + # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' + # 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] + + def feed(self, byte_str): + # Final letter analysis for logical-visual decision. + # Look for evidence that the received buffer is either logical Hebrew + # or visual Hebrew. + # The following cases are checked: + # 1) A word longer than 1 letter, ending with a final letter. This is + # an indication that the text is laid out "naturally" since the + # final letter really appears at the end. +1 for logical score. + # 2) A word longer than 1 letter, ending with a Non-Final letter. In + # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, + # should not end with the Non-Final form of that letter. Exceptions + # to this rule are mentioned above in isNonFinal(). This is an + # indication that the text is laid out backwards. +1 for visual + # score + # 3) A word longer than 1 letter, starting with a final letter. Final + # letters should not appear at the beginning of a word. This is an + # indication that the text is laid out backwards. +1 for visual + # score. + # + # The visual score and logical score are accumulated throughout the + # text and are finally checked against each other in GetCharSetName(). + # No checking for final letters in the middle of words is done since + # that case is not an indication for either Logical or Visual text. + # + # We automatically filter out all 7-bit characters (replace them with + # spaces) so the word boundary detection works properly. [MAP] + + if self.state == ProbingState.NOT_ME: + # Both model probers say it's not them. No reason to continue. + return ProbingState.NOT_ME + + byte_str = self.filter_high_byte_only(byte_str) + + for cur in byte_str: + if cur == ' ': + # We stand on a space - a word just ended + if self._before_prev != ' ': + # next-to-last char was not a space so self._prev is not a + # 1 letter word + if self.is_final(self._prev): + # case (1) [-2:not space][-1:final letter][cur:space] + self._final_char_logical_score += 1 + elif self.is_non_final(self._prev): + # case (2) [-2:not space][-1:Non-Final letter][ + # cur:space] + self._final_char_visual_score += 1 + else: + # Not standing on a space + if ((self._before_prev == ' ') and + (self.is_final(self._prev)) and (cur != ' ')): + # case (3) [-2:space][-1:final letter][cur:not space] + self._final_char_visual_score += 1 + self._before_prev = self._prev + self._prev = cur + + # Forever detecting, till the end or until both model probers return + # ProbingState.NOT_ME (handled above) + return ProbingState.DETECTING + + @property + def charset_name(self): + # 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 + if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: + 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()) + if modelsub > self.MIN_MODEL_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if modelsub < -self.MIN_MODEL_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # Still no good, back to final letter distance, maybe it'll save the + # day. + if finalsub < 0.0: + return self.VISUAL_HEBREW_NAME + + # (finalsub > 0 - Logical) or (don't know what to do) default to + # Logical. + return self.LOGICAL_HEBREW_NAME + + @property + def language(self): + return 'Hebrew' + + @property + def state(self): + # 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): + return ProbingState.NOT_ME + return ProbingState.DETECTING diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jisfreq.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jisfreq.py new file mode 100644 index 0000000..83fc082 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jisfreq.py @@ -0,0 +1,325 @@ +######################## 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 ######################### + +# Sampling from about 20M text materials include literature and computer technology +# +# Japanese frequency table, applied to both S-JIS and EUC-JP +# They are sorted in order. + +# 128 --> 0.77094 +# 256 --> 0.85710 +# 512 --> 0.92635 +# 1024 --> 0.97130 +# 2048 --> 0.99431 +# +# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 +# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 +# +# Typical Distribution Ratio, 25% of IDR + +JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 + +# Char to FreqOrder table , +JIS_TABLE_SIZE = 4368 + +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 +1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 +2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 +2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 +5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 +1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 +5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 +5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 +5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 +5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 +5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 +5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 +1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 +1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 +1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 +2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 +3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 +3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 + 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 + 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 +1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 + 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 +5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 + 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 + 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 + 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 + 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 + 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 +5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 +5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 +5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 +4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 +5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 +5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 +5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 +5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 +5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 +5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 +5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 +5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 +5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 +3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 +5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 +5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 +5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 +5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 +5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 +5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 +5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 +5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 +5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 +5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 +5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 +5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 +5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 +5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 +5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 +5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 +5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 +5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 +5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 +5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 +5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 +5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 +5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 +5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 +5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 +5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 +5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 +5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 +5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 +5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 +5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 +5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 +5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 +5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 +5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 +5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 +6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 +6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 +6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 +6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 +6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 +6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 +6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 +6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 +4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 + 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 + 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 +1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 +1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 + 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 +3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 +3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 + 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 +3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 +3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 + 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 +2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 + 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 +3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 +1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 + 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 +1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 + 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 +2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 +2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 +2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 +2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 +1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 +1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 +1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 +1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 +2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 +1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 +2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 +1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 +1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 +1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 +1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 +1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 +1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 + 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 + 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 +1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 +2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 +2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 +2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 +3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 +3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 + 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 +3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 +1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 + 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 +2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 +1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 + 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 +3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 +4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 +2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 +1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 +2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 +1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 + 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 + 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 +1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 +2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 +2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 +2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 +3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 +1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 +2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 + 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 + 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 + 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 +1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 +2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 + 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 +1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 +1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 + 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 +1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 +1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 +1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 + 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 +2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 + 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 +2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 +3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 +2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 +1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 +6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 +1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 +2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 +1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 + 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 + 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 +3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 +3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 +1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 +1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 +1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 +1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 + 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 + 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 +2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 + 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 +3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 +2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 + 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 +1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 +2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 + 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 +1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 + 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 +4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 +2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 +1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 + 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 +1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 +2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 + 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 +6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 +1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 +1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 +2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 +3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 + 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 +3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 +1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 + 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 +1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 + 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 +3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 + 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 +2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 + 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 +4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 +2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 +1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 +1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 +1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 + 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 +1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 +3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 +1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 +3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 + 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 + 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 + 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 +2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 +1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 + 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 +1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 + 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 +1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 + 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 + 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 + 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 +1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 +1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 +2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 +4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 + 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 +1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 + 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 +1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 +3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 +1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 +2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 +2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 +1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 +1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 +2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 + 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 +2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 +1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 +1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 +1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 +1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 +3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 +2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 +2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 + 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 +3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 +3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 +1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 +2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 +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 +) + + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jpcntx.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jpcntx.py new file mode 100644 index 0000000..20044e4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/jpcntx.py @@ -0,0 +1,233 @@ +######################## 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 ######################### + + +# 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), +) + +class JapaneseContextAnalysis(object): + 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 + self.reset() + + def reset(self): + self._total_rel = 0 # total sequence received + # category counters, each integer counts sequence in its category + self._rel_sample = [0] * self.NUM_OF_CATEGORY + # if last byte in current buffer is not the last byte of a character, + # we need to know how many bytes to skip in next buffer + self._need_to_skip_char_num = 0 + self._last_char_order = -1 # The order of previous char + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + + def feed(self, byte_str, num_bytes): + if self._done: + return + + # The buffer we got is byte oriented, and a character may span in more than one + # buffers. In case the last one or two byte in last buffer is not + # complete, we record how many byte needed to complete that character + # and skip these bytes here. We can choose to record those bytes as + # well and analyse the character once it is complete, but since a + # character will not make much difference, by simply skipping + # 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]) + i += char_len + if i > num_bytes: + self._need_to_skip_char_num = i - num_bytes + self._last_char_order = -1 + else: + if (order != -1) and (self._last_char_order != -1): + self._total_rel += 1 + if self._total_rel > self.MAX_REL_THRESHOLD: + self._done = True + break + self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1 + self._last_char_order = order + + def got_enough_data(self): + return self._total_rel > self.ENOUGH_REL_THRESHOLD + + def get_confidence(self): + # 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 + + def get_order(self, byte_str): + return -1, 1 + +class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self): + super(SJISContextAnalysis, self).__init__() + self._charset_name = "SHIFT_JIS" + + @property + def charset_name(self): + return self._charset_name + + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): + char_len = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self._charset_name = "CP932" + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 202) and (0x9F <= second_char <= 0xF1): + return second_char - 0x9F, char_len + + return -1, char_len + +class EUCJPContextAnalysis(JapaneseContextAnalysis): + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): + char_len = 2 + elif first_char == 0x8F: + char_len = 3 + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): + return second_char - 0xA1, char_len + + return -1, char_len + + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langbulgarianmodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langbulgarianmodel.py new file mode 100644 index 0000000..2aa4fb2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langbulgarianmodel.py @@ -0,0 +1,228 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +# this table is modified base on win1251BulgarianCharToOrderMap, so +# only number <64 is sure valid + +Latin5_BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 +210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 + 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 + 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 +) + +win1251BulgarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 +110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 +253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 +116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 +206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 +221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 + 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 + 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 + 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 + 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 + 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 + 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 96.9392% +# first 1024 sequences:3.0618% +# rest sequences: 0.2992% +# negative sequences: 0.0020% +BulgarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, +3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, +0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, +0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, +0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, +1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, +0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,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, +1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, +0,0,0,0,0,0,0,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, +2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,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, +3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,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,1, +3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,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,1, +3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,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, +2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,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,1, +3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,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, +3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,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, +3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, +2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, +3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, +3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, +1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, +3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, +1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, +2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, +2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, +3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, +1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, +2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, +2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, +1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, +2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, +2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, +2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, +1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, +2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, +1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, +3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, +1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, +3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, +1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, +2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, +1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, +2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,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,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, +1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, +2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, +1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, +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,1,0,1,0, +1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, +2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, +1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, +2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, +1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, +0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, +1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, +1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, +1,0,0,1,3,1,1,0,0,0,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,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, +0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, +1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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,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,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, +1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, +1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,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,0,0,0,0,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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,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,1, +) + +Latin5BulgarianModel = { + 'char_to_order_map': Latin5_BulgarianCharToOrderMap, + 'precedence_matrix': BulgarianLangModel, + 'typical_positive_ratio': 0.969392, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-5", + 'language': 'Bulgairan', +} + +Win1251BulgarianModel = { + 'char_to_order_map': win1251BulgarianCharToOrderMap, + 'precedence_matrix': BulgarianLangModel, + 'typical_positive_ratio': 0.969392, + 'keep_english_letter': False, + 'charset_name': "windows-1251", + 'language': 'Bulgarian', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langcyrillicmodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langcyrillicmodel.py new file mode 100644 index 0000000..e5f9a1f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langcyrillicmodel.py @@ -0,0 +1,333 @@ +######################## 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 ######################### + +# KOI8-R language model +# Character Mapping Table: +KOI8R_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 +223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 +238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 + 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 + 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 + 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 + 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 +) + +win1251_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +) + +latin5_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +macCyrillic_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, +239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, +) + +IBM855_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 +191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, +206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, + 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, +220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, +230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, + 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, + 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, +250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, +) + +IBM866_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 +155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 +253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 + 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 + 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, + 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, + 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, +191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, +207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, +223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, + 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, +239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 97.6601% +# first 1024 sequences: 2.3389% +# rest sequences: 0.1237% +# negative sequences: 0.0009% +RussianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, +0,0,0,0,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, +3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, +0,0,0,0,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, +3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, +0,0,0,0,0,1,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, +3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, +0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, +0,0,0,0,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, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, +0,0,0,0,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, +3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,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, +3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, +0,0,0,0,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, +3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, +0,0,0,0,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, +2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,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, +3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,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, +3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, +0,0,0,0,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, +3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,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, +3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,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, +3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,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,2,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,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, +3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, +0,0,0,0,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, +3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,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, +2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, +0,0,0,0,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, +3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, +0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,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, +3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,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, +2,1,3,3,3,2,0,0,0,2,1,0,1,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, +1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, +1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, +2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, +1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, +3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, +1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, +2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, +1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, +1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, +1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, +1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, +3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, +1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, +2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, +1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, +2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, +0,0,0,0,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, +2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, +1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, +1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, +1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, +3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, +3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, +1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, +1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, +0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, +1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, +1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, +0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, +1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, +2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, +2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, +1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, +1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, +2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, +1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, +0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, +2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, +1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, +1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, +0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,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, +1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, +0,1,1,0,0,1,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, +1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, +0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, +1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, +1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, +0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, +0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, +2,2,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,1, +1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,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,1, +1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,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,1, +1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, +0,1,1,1,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, +0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +) + +Koi8rModel = { + 'char_to_order_map': KOI8R_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "KOI8-R", + 'language': 'Russian', +} + +Win1251CyrillicModel = { + 'char_to_order_map': win1251_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "windows-1251", + 'language': 'Russian', +} + +Latin5CyrillicModel = { + 'char_to_order_map': latin5_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-5", + 'language': 'Russian', +} + +MacCyrillicModel = { + 'char_to_order_map': macCyrillic_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "MacCyrillic", + 'language': 'Russian', +} + +Ibm866Model = { + 'char_to_order_map': IBM866_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "IBM866", + 'language': 'Russian', +} + +Ibm855Model = { + 'char_to_order_map': IBM855_char_to_order_map, + 'precedence_matrix': RussianLangModel, + 'typical_positive_ratio': 0.976601, + 'keep_english_letter': False, + 'charset_name': "IBM855", + 'language': 'Russian', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langgreekmodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langgreekmodel.py new file mode 100644 index 0000000..5332221 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langgreekmodel.py @@ -0,0 +1,225 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin7_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +win1253_char_to_order_map = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 + 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 +253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 + 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 +253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 +253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 +110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 + 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 +124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 + 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.2851% +# first 1024 sequences:1.7001% +# rest sequences: 0.0359% +# negative sequences: 0.0148% +GreekLangModel = ( +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,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,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, +3,0,0,0,2,0,0,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,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,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,2,0,0,0,0,0,0,0,0, +0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, +2,0,0,0,2,0,0,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,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, +0,0,0,0,2,0,0,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,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, +2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, +2,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,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,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,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,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,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,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,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, +0,0,1,0,0,0,0,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,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,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, +0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, +2,0,0,0,0,0,0,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,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,2,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,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,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, +3,0,0,0,0,0,0,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,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,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,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, +3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, +2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, +2,0,0,0,0,0,0,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,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,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,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,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,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, +0,0,0,0,1,0,0,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,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,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,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,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,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,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,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, +0,0,0,0,0,0,0,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,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,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,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,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,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,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,0, +0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,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,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, +0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, +0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,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,0, +0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, +0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, +0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, +0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, +0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, +0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,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,0,0,0, +0,0,0,0,0,0,0,0,0,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,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, +0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, +0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, +0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, +0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,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,0,0,0,0, +0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, +0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, +0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, +0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,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,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, +0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, +0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, +0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, +0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, +0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, +0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, +0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, +0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, +0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,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,0,0,0,0,0, +0,0,0,0,0,0,1,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,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, +0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, +0,0,0,0,0,0,0,2,1,0,0,0,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,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,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,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,0,0,1,0,0,1,0,2,0,0,0, +0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,1,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,0,0,0,0,0,0, +0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, +0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,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,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin7GreekModel = { + 'char_to_order_map': Latin7_char_to_order_map, + 'precedence_matrix': GreekLangModel, + 'typical_positive_ratio': 0.982851, + 'keep_english_letter': False, + 'charset_name': "ISO-8859-7", + 'language': 'Greek', +} + +Win1253GreekModel = { + 'char_to_order_map': win1253_char_to_order_map, + 'precedence_matrix': GreekLangModel, + 'typical_positive_ratio': 0.982851, + 'keep_english_letter': False, + 'charset_name': "windows-1253", + 'language': 'Greek', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhebrewmodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhebrewmodel.py new file mode 100644 index 0000000..58f4c87 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhebrewmodel.py @@ -0,0 +1,200 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Simon Montagu +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Shoshannah Forbes - 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Windows-1255 language model +# Character Mapping Table: +WIN1255_CHAR_TO_ORDER_MAP = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 + 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 +253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 + 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 +124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, +215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, + 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, +106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, + 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, +238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, + 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, + 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 98.4004% +# first 1024 sequences: 1.5981% +# rest sequences: 0.087% +# negative sequences: 0.0015% +HEBREW_LANG_MODEL = ( +0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, +3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, +1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, +1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, +1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, +1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, +0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, +1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, +0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, +0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, +0,2,0,1,2,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,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, +0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, +0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, +0,2,0,1,2,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,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, +0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, +0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, +0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, +0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, +3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, +0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, +0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, +0,2,1,1,1,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,1,0, +3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, +0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, +3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,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, +3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,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, +3,0,0,0,1,1,0,1,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,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, +0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, +2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,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, +2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, +0,0,0,1,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, +2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,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, +3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,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,1,0,0,0,0,0,0,0,0,0, +0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, +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,0,0, +3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,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, +1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, +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,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, +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, +2,1,2,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,2,2,2, +0,2,0,1,2,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,1,0, +3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,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, +2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, +0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,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, +2,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, +1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, +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,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, +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,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, +1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, +0,2,0,1,2,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, +2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,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, +1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,1,0,1,0,0,1,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, +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,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, +1,0,0,1,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,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, +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,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, +2,0,1,0,0,1,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,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, +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,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, +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,0,0,0, +0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, +2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,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, +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,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, +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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,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,0,0,0,0, +0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,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, +1,0,0,1,0,0,1,0,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,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, +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,0,0,0, +1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, +0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,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, +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,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, +) + +Win1255HebrewModel = { + 'char_to_order_map': WIN1255_CHAR_TO_ORDER_MAP, + 'precedence_matrix': HEBREW_LANG_MODEL, + 'typical_positive_ratio': 0.984004, + 'keep_english_letter': False, + 'charset_name': "windows-1255", + 'language': 'Hebrew', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhungarianmodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhungarianmodel.py new file mode 100644 index 0000000..bb7c095 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langhungarianmodel.py @@ -0,0 +1,225 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin2_HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, +175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, + 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, + 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, +245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +win1250HungarianCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, + 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, +253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, + 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, +161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, +177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, +191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, + 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, +221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, +232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, + 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, +245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 94.7368% +# first 1024 sequences:5.2623% +# rest sequences: 0.8894% +# negative sequences: 0.0009% +HungarianLangModel = ( +0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, +3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, +3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,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,2,0, +3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,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,1,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, +0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, +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,1,0, +3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,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,1,0,0,0,0,0,1,0, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, +3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,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, +3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,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, +3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,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,1,0, +3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,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, +3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,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, +3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, +2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, +0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, +3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, +1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, +1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,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, +1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,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, +3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,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,0, +3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, +3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, +2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, +2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, +2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, +2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, +2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, +1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, +1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, +3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, +1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, +1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, +2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, +2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, +2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, +3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,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, +2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, +2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, +1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, +1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, +2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, +1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, +2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, +1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, +1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, +2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, +2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, +2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, +1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, +1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, +0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, +2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, +1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, +2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, +1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, +1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, +2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, +2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, +2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,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, +1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, +1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, +2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, +0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, +2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,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,0,0,0,0,0,0,0,0, +1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, +) + +Latin2HungarianModel = { + 'char_to_order_map': Latin2_HungarianCharToOrderMap, + 'precedence_matrix': HungarianLangModel, + 'typical_positive_ratio': 0.947368, + 'keep_english_letter': True, + 'charset_name': "ISO-8859-2", + 'language': 'Hungarian', +} + +Win1250HungarianModel = { + 'char_to_order_map': win1250HungarianCharToOrderMap, + 'precedence_matrix': HungarianLangModel, + 'typical_positive_ratio': 0.947368, + 'keep_english_letter': True, + 'charset_name': "windows-1250", + 'language': 'Hungarian', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langthaimodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langthaimodel.py new file mode 100644 index 0000000..15f94c2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langthaimodel.py @@ -0,0 +1,199 @@ +######################## 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# The following result for thai was collected from a limited sample (1M). + +# Character Mapping Table: +TIS620CharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 +253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 +252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 +253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 +188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 +253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 + 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 +209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, +223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, +236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, + 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, + 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, + 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, + 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, + 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, +) + +# Model Table: +# total sequences: 100% +# first 512 sequences: 92.6386% +# first 1024 sequences:7.3177% +# rest sequences: 1.0230% +# negative sequences: 0.0436% +ThaiLangModel = ( +0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, +0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, +3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, +0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, +3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, +3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, +3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, +3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, +3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, +2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, +3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, +0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, +1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, +3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, +3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, +1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, +0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, +0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, +3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, +2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, +3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, +0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, +3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, +2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, +3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, +2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, +3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, +3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, +3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, +3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, +3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, +1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, +0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, +3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, +0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, +3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, +3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, +1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, +3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, +3,1,0,2,3,0,0,0,3,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,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,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,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, +0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, +0,0,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, +3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, +1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, +1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, +3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, +0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, +0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, +3,0,0,1,1,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, +3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, +0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, +0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, +0,1,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, +3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, +0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, +0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, +0,0,0,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,3,3,3,3,1,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, +1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, +0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, +3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, +0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, +0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, +3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, +2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, +0,0,0,2,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,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, +3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, +2,0,0,0,0,0,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, +2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,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,0,0,0,0,0,0,0,0,0,0,0, +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,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,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, +1,0,0,1,0,1,1,0,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,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, +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,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,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, +2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, +1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, +1,0,0,3,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,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, +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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, +2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,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,0, +2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,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, +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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, +1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,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,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,1,0,0,2,1,0,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,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, +2,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +TIS620ThaiModel = { + 'char_to_order_map': TIS620CharToOrderMap, + 'precedence_matrix': ThaiLangModel, + 'typical_positive_ratio': 0.926386, + 'keep_english_letter': False, + 'charset_name': "TIS-620", + 'language': 'Thai', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langturkishmodel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langturkishmodel.py new file mode 100644 index 0000000..a427a45 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/langturkishmodel.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +######################## 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 +# Özgür Baskın - Turkish Language Model +# +# 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 ######################### + +# 255: Control characters that usually does not exist in any text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 + +# Character Mapping Table: +Latin5_TurkishCharToOrderMap = ( +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255, 23, 37, 47, 39, 29, 52, 36, 45, 53, 60, 16, 49, 20, 46, 42, + 48, 69, 44, 35, 31, 51, 38, 62, 65, 43, 56,255,255,255,255,255, +255, 1, 21, 28, 12, 2, 18, 27, 25, 3, 24, 10, 5, 13, 4, 15, + 26, 64, 7, 8, 9, 14, 32, 57, 58, 11, 22,255,255,255,255,255, +180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165, +164,163,162,161,160,159,101,158,157,156,155,154,153,152,151,106, +150,149,148,147,146,145,144,100,143,142,141,140,139,138,137,136, + 94, 80, 93,135,105,134,133, 63,132,131,130,129,128,127,126,125, +124,104, 73, 99, 79, 85,123, 54,122, 98, 92,121,120, 91,103,119, + 68,118,117, 97,116,115, 50, 90,114,113,112,111, 55, 41, 40, 86, + 89, 70, 59, 78, 71, 82, 88, 33, 77, 66, 84, 83,110, 75, 61, 96, + 30, 67,109, 74, 87,102, 34, 95, 81,108, 76, 72, 17, 6, 19,107, +) + +TurkishLangModel = ( +3,2,3,3,3,1,3,3,3,3,3,3,3,3,2,1,1,3,3,1,3,3,0,3,3,3,3,3,0,3,1,3, +3,2,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, +3,2,2,3,3,0,3,3,3,3,3,3,3,2,3,1,0,3,3,1,3,3,0,3,3,3,3,3,0,3,0,3, +3,1,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,0,1,0,1, +3,3,2,3,3,0,3,3,3,3,3,3,3,2,3,1,1,3,3,0,3,3,1,2,3,3,3,3,0,3,0,3, +3,1,1,0,0,0,1,0,0,0,0,1,1,0,1,2,1,0,0,0,1,0,0,0,0,2,0,0,0,0,0,1, +3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1,3,3,2,0,3,2,1,2,2,1,3,3,0,0,0,2, +2,2,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,1, +3,3,3,2,3,3,1,2,3,3,3,3,3,3,3,1,3,2,1,0,3,2,0,1,2,3,3,2,1,0,0,2, +2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0, +1,0,1,3,3,1,3,3,3,3,3,3,3,1,2,0,0,2,3,0,2,3,0,0,2,2,2,3,0,3,0,1, +2,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, +3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,0,3,2,0,2,3,2,3,3,1,0,0,2, +3,2,0,0,1,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,2,0,0,1, +3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,0,3,3,0,0,2,1,0,0,2,3,2,2,0,0,0,2, +2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,0,2,0,0,1, +3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,0,1,3,2,1,1,3,2,3,2,1,0,0,2, +2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, +3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,2,0,2,3,0,0,2,2,2,2,0,0,0,2, +3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, +3,3,3,3,3,3,3,2,2,2,2,3,2,3,3,0,3,3,1,1,2,2,0,0,2,2,3,2,0,0,1,3, +0,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1, +3,3,3,2,3,3,3,2,1,2,2,3,2,3,3,0,3,2,0,0,1,1,0,1,1,2,1,2,0,0,0,1, +0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0, +3,3,3,2,3,3,2,3,2,2,2,3,3,3,3,1,3,1,1,0,3,2,1,1,3,3,2,3,1,0,0,1, +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,1,0,2,0,0,1, +3,2,2,3,3,0,3,3,3,3,3,3,3,2,2,1,0,3,3,1,3,3,0,1,3,3,2,3,0,3,0,3, +2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, +2,2,2,3,3,0,3,3,3,3,3,3,3,3,3,0,0,3,2,0,3,3,0,3,2,3,3,3,0,3,1,3, +2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, +3,3,3,1,2,3,3,1,0,0,1,0,0,3,3,2,3,0,0,2,0,0,2,0,2,0,0,0,2,0,2,0, +0,3,1,0,1,0,0,0,2,2,1,0,1,1,2,1,2,2,2,0,2,1,1,0,0,0,2,0,0,0,0,0, +1,2,1,3,3,0,3,3,3,3,3,2,3,0,0,0,0,2,3,0,2,3,1,0,2,3,1,3,0,3,0,2, +3,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, +3,3,3,1,3,3,2,2,3,2,2,0,1,2,3,0,1,2,1,0,1,0,0,0,1,0,2,2,0,0,0,1, +1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0, +3,3,3,1,3,3,1,1,3,3,1,1,3,3,1,0,2,1,2,0,2,1,0,0,1,1,2,1,0,0,0,2, +2,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, +3,3,3,1,0,2,1,3,0,0,2,0,0,3,3,0,3,0,0,1,0,1,2,0,0,1,1,2,2,0,1,0, +0,1,2,1,1,0,1,0,1,1,1,1,1,0,1,1,1,2,2,1,2,0,1,0,0,0,0,0,0,1,0,0, +3,3,3,2,3,2,3,3,0,2,2,2,3,3,3,0,3,0,0,0,2,2,0,1,2,1,1,1,0,0,0,1, +0,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,1,0,0,0, +3,3,3,3,3,3,2,1,2,2,3,3,3,3,2,0,2,0,0,0,2,2,0,0,2,1,3,3,0,0,1,1, +1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0, +1,1,2,3,3,0,3,3,3,3,3,3,2,2,0,2,0,2,3,2,3,2,2,2,2,2,2,2,1,3,2,3, +2,0,2,1,2,2,2,2,1,1,2,2,1,2,2,1,2,0,0,2,1,1,0,2,1,0,0,1,0,0,0,1, +2,3,3,1,1,1,0,1,1,1,2,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,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, +3,3,3,2,2,2,3,2,3,2,2,1,3,3,3,0,2,1,2,0,2,1,0,0,1,1,1,1,1,0,0,1, +2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, +3,3,3,2,3,3,3,3,3,2,3,1,2,3,3,1,2,0,0,0,0,0,0,0,3,2,1,1,0,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,1,0,0,0,0,0,0, +3,3,3,2,2,3,3,2,1,1,1,1,1,3,3,0,3,1,0,0,1,1,0,0,3,1,2,1,0,0,0,0, +0,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,1,0,0,0,0,0, +3,3,3,2,2,3,2,2,2,3,2,1,1,3,3,0,3,0,0,0,0,1,0,0,3,1,1,2,0,0,0,1, +1,0,0,1,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,1, +1,1,1,3,3,0,3,3,3,3,3,2,2,2,1,2,0,2,1,2,2,1,1,0,1,2,2,2,2,2,2,2, +0,0,2,1,2,1,2,1,0,1,1,3,1,2,1,1,2,0,0,2,0,1,0,1,0,1,0,0,0,1,0,1, +3,3,3,1,3,3,3,0,1,1,0,2,2,3,1,0,3,0,0,0,1,0,0,0,1,0,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,0,0,0,0,0,0, +3,3,2,0,0,2,2,1,0,0,1,0,0,3,3,1,3,0,0,1,1,0,2,0,3,0,0,0,2,0,1,1, +0,1,2,0,1,2,2,0,2,2,2,2,1,0,2,1,1,0,2,0,2,1,2,0,0,0,0,0,0,0,0,0, +3,3,3,1,3,2,3,2,0,2,2,2,1,3,2,0,2,1,2,0,1,2,0,0,1,0,2,2,0,0,0,2, +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,1,1,0,1,0,0,0, +3,3,3,0,3,3,1,1,2,3,1,0,3,2,3,0,3,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0, +1,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,3,3,0,3,3,2,3,3,2,2,0,0,0,0,1,2,0,1,3,0,0,0,3,1,1,0,3,0,2, +2,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, +3,3,3,1,2,2,1,0,3,1,1,1,1,3,3,2,3,0,0,1,0,1,2,0,2,2,0,2,2,0,2,1, +0,2,2,1,1,1,1,0,2,1,1,0,1,1,1,1,2,1,2,1,2,0,1,0,1,0,0,0,0,0,0,0, +3,3,3,0,1,1,3,0,0,1,1,0,0,2,2,0,3,0,0,1,1,0,1,0,0,0,0,0,2,0,0,0, +0,3,1,0,1,0,1,0,2,0,0,1,0,1,0,1,1,1,2,1,1,0,2,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,0,2,0,1,1,1,0,0,3,3,0,2,0,0,1,0,0,2,1,1,0,1,0,1,0,1,0, +0,2,0,1,2,0,2,0,2,1,1,0,1,0,2,1,1,0,2,1,1,0,1,0,0,0,1,1,0,0,0,0, +3,2,3,0,1,0,0,0,0,0,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,0,2,0,0,0, +0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,2,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,2,3,0,0,1,0,1,0,2,3,2,3,0,0,1,3,0,2,1,0,0,0,0,2,0,1,0, +0,2,1,0,0,1,1,0,2,1,0,0,1,0,0,1,1,0,1,1,2,0,1,0,0,0,0,1,0,0,0,0, +3,2,2,0,0,1,1,0,0,0,0,0,0,3,1,1,1,0,0,0,0,0,1,0,0,0,0,0,2,0,1,0, +0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,3,3,0,2,3,2,2,1,2,2,1,1,2,0,1,3,2,2,2,0,0,2,2,0,0,0,1,2,1, +3,0,2,1,1,0,1,1,1,0,1,2,2,2,1,1,2,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0, +0,1,1,2,3,0,3,3,3,2,2,2,2,1,0,1,0,1,0,1,2,2,0,0,2,2,1,3,1,1,2,1, +0,0,1,1,2,0,1,1,0,0,1,2,0,2,1,1,2,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0, +3,3,2,0,0,3,1,0,0,0,0,0,0,3,2,1,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, +0,2,1,1,0,0,1,0,1,2,0,0,1,1,0,0,2,1,1,1,1,0,2,0,0,0,0,0,0,0,0,0, +3,3,2,0,0,1,0,0,0,0,1,0,0,3,3,2,2,0,0,1,0,0,2,0,1,0,0,0,2,0,1,0, +0,0,1,1,0,0,2,0,2,1,0,0,1,1,2,1,2,0,2,1,2,1,1,1,0,0,1,1,0,0,0,0, +3,3,2,0,0,2,2,0,0,0,1,1,0,2,2,1,3,1,0,1,0,1,2,0,0,0,0,0,1,0,1,0, +0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,2,0,0,0,1,0,0,1,0,0,2,3,1,2,0,0,1,0,0,2,0,0,0,1,0,2,0,2,0, +0,1,1,2,2,1,2,0,2,1,1,0,0,1,1,0,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,1,2,1,0,0,1,1,0,3,3,1,2,0,0,1,0,0,2,0,2,0,1,1,2,0,0,0, +0,0,1,1,1,1,2,0,1,1,0,1,1,1,1,0,0,0,1,1,1,0,1,0,0,0,1,0,0,0,0,0, +3,3,3,0,2,2,3,2,0,0,1,0,0,2,3,1,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,0, +0,1,1,0,0,0,1,0,0,1,0,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, +3,2,3,0,0,0,0,0,0,0,1,0,0,2,2,2,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, +0,0,2,1,1,0,1,0,2,1,1,0,0,1,1,2,1,0,2,0,2,0,1,0,0,0,2,0,0,0,0,0, +0,0,0,2,2,0,2,1,1,1,1,2,2,0,0,1,0,1,0,0,1,3,0,0,0,0,1,0,0,2,1,0, +0,0,1,0,1,0,0,0,0,0,2,1,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, +2,0,0,2,3,0,2,3,1,2,2,0,2,0,0,2,0,2,1,1,1,2,1,0,0,1,2,1,1,2,1,0, +1,0,2,0,1,0,1,1,0,0,2,2,1,2,1,1,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,2,1,2,0,0,0,1,0,0,3,2,0,1,0,0,1,0,0,2,0,0,0,1,2,1,0,1,0, +0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,2,2,0,2,2,1,1,0,1,1,1,1,1,0,0,1,2,1,1,1,0,1,0,0,0,1,1,1,1, +0,0,2,1,0,1,1,1,0,1,1,2,1,2,1,1,2,0,1,1,2,1,0,2,0,0,0,0,0,0,0,0, +3,2,2,0,0,2,0,0,0,0,0,0,0,2,2,0,2,0,0,1,0,0,2,0,0,0,0,0,2,0,0,0, +0,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, +0,0,0,3,2,0,2,2,0,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0, +2,0,1,0,1,0,1,1,0,0,1,2,0,1,0,1,1,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0, +2,2,2,0,1,1,0,0,0,1,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,1,2,0,1,0, +0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, +2,2,2,2,1,0,1,1,1,0,0,0,0,1,2,0,0,1,0,0,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,0,0,0,0,0,0,2,0,0,0,0,0,0,0, +1,1,2,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,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,1,0,0,0,0,0,2,0,0,0,0,0,1, +0,0,1,2,2,0,2,1,2,1,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,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,1,0,0,0, +2,2,2,0,0,0,1,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,1,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,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,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, +2,2,2,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,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,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,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,2,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +) + +Latin5TurkishModel = { + 'char_to_order_map': Latin5_TurkishCharToOrderMap, + 'precedence_matrix': TurkishLangModel, + 'typical_positive_ratio': 0.970290, + 'keep_english_letter': True, + 'charset_name': "ISO-8859-9", + 'language': 'Turkish', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/latin1prober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/latin1prober.py new file mode 100644 index 0000000..7d1e8c2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/latin1prober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 .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 +CLASS_NUM = 8 # total classes + +Latin1_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 + OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 + OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F + UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 + OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF + ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF + ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 + ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF + ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 + ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +Latin1ClassModel = ( +# UDF OTH ASC ASS ACV ACO ASV ASO + 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, # ASO +) + + +class Latin1Prober(CharSetProber): + def __init__(self): + super(Latin1Prober, self).__init__() + self._last_char_class = None + self._freq_counter = None + self.reset() + + def reset(self): + self._last_char_class = OTH + self._freq_counter = [0] * FREQ_CAT_NUM + CharSetProber.reset(self) + + @property + def charset_name(self): + return "ISO-8859-1" + + @property + def language(self): + return "" + + def feed(self, byte_str): + byte_str = self.filter_with_english_letters(byte_str) + for c in byte_str: + char_class = Latin1_CharToClass[c] + freq = Latin1ClassModel[(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): + 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 + # lower the confidence of latin1 so that other more accurate + # detector can take priority. + confidence = confidence * 0.73 + return confidence diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcharsetprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcharsetprober.py new file mode 100644 index 0000000..6256ecf --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcharsetprober.py @@ -0,0 +1,91 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# 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 .charsetprober import CharSetProber +from .enums import ProbingState, MachineState + + +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 reset(self): + super(MultiByteCharSetProber, self).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 + + @property + def language(self): + raise NotImplementedError + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + 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: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.distribution_analyzer.feed(self._last_char, char_len) + else: + 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)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + return self.distribution_analyzer.get_confidence() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcsgroupprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcsgroupprober.py new file mode 100644 index 0000000..530abe7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcsgroupprober.py @@ -0,0 +1,54 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# 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 .charsetgroupprober import CharSetGroupProber +from .utf8prober import UTF8Prober +from .sjisprober import SJISProber +from .eucjpprober import EUCJPProber +from .gb2312prober import GB2312Prober +from .euckrprober import EUCKRProber +from .cp949prober import CP949Prober +from .big5prober import Big5Prober +from .euctwprober import EUCTWProber + + +class MBCSGroupProber(CharSetGroupProber): + def __init__(self, lang_filter=None): + super(MBCSGroupProber, self).__init__(lang_filter=lang_filter) + self.probers = [ + UTF8Prober(), + SJISProber(), + EUCJPProber(), + GB2312Prober(), + EUCKRProber(), + CP949Prober(), + Big5Prober(), + EUCTWProber() + ] + self.reset() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcssm.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcssm.py new file mode 100644 index 0000000..8360d0f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/mbcssm.py @@ -0,0 +1,572 @@ +######################## 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): +# 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 ######################### + +from .enums import MachineState + +# BIG5 + +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 +) + +BIG5_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + 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 +) + +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'} + +# CP949 + +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 +) + +CP949_ST = ( +#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR + 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.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 + 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 +) + +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'} + +# EUC-JP + +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 +) + +EUCJP_ST = ( + 3, 4, 3, 5,MachineState.START,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.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + 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 +) + +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'} + +# EUC-KR + +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, # 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 +) + +EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) + +EUCKR_SM_MODEL = {'class_table': EUCKR_CLS, + 'class_factor': 4, + 'state_table': EUCKR_ST, + 'char_len_table': EUCKR_CHAR_LEN_TABLE, + 'name': 'EUC-KR'} + +# EUC-TW + +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 +) + +EUCTW_ST = ( + MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 + MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 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 +) + +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'} + +# GB2312 + +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 +) + +GB2312_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 + 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 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 +) + +# 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 +# it is used for frequency analysis only, and we are validating +# each code range there as well. So it is safe to set it to be +# 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'} + +# Shift_JIS + +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 + #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 + + +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 +) + +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'} + +# UCS2-BE + +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 +) + +UCS2BE_ST = ( + 5, 7, 7,MachineState.ERROR, 4, 3,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, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 + 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f + 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 + 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 +) + +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'} + +# UCS2-LE + +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 +) + +UCS2LE_ST = ( + 6, 6, 7, 6, 4, 3,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, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f + 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 + 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 +) + +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'} + +# UTF-8 + +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 +) + +UTF8_ST = ( + MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 + 9, 11, 8, 7, 6, 5, 4, 3,#08-0f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f + MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f + MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f + MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f + MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af + MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf + 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 +) + +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'} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcharsetprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcharsetprober.py new file mode 100644 index 0000000..0adb51d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcharsetprober.py @@ -0,0 +1,132 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 .charsetprober import CharSetProber +from .enums import CharacterCategory, ProbingState, SequenceLikelihood + + +class SingleByteCharSetProber(CharSetProber): + SAMPLE_SIZE = 64 + 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__() + self._model = model + # TRUE if we need to reverse every pair in the model lookup + self._reversed = 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.reset() + + def reset(self): + super(SingleByteCharSetProber, self).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 + # characters that fall in our sampling range + self._freq_char = 0 + + @property + def charset_name(self): + if self._name_prober: + return self._name_prober.charset_name + else: + return self._model['charset_name'] + + @property + def language(self): + if self._name_prober: + return self._name_prober.language + else: + return self._model.get('language') + + def feed(self, byte_str): + if not self._model['keep_english_letter']: + byte_str = self.filter_international_words(byte_str) + if not byte_str: + return self.state + char_to_order_map = self._model['char_to_order_map'] + for i, c in enumerate(byte_str): + # XXX: Order is in range 1-64, so one would think we want 0-63 here, + # but that leads to 27 more test failures than before. + order = char_to_order_map[c] + # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but + # CharacterCategory.SYMBOL is actually 253, so we use CONTROL + # to make it closer to the original intent. The only difference + # is whether or not we count digits and control characters for + # _total_char purposes. + if order < CharacterCategory.CONTROL: + self._total_char += 1 + if order < self.SAMPLE_SIZE: + self._freq_char += 1 + if self._last_order < self.SAMPLE_SIZE: + self._total_seqs += 1 + if not self._reversed: + i = (self._last_order * self.SAMPLE_SIZE) + order + model = self._model['precedence_matrix'][i] + else: # reverse the order of the letters in the lookup + i = (order * self.SAMPLE_SIZE) + self._last_order + model = self._model['precedence_matrix'][i] + self._seq_counters[model] += 1 + self._last_order = order + + charset_name = self._model['charset_name'] + if self.state == ProbingState.DETECTING: + 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._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._state = ProbingState.NOT_ME + + return self.state + + def get_confidence(self): + 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 = r * self._freq_char / self._total_char + if r >= 1.0: + r = 0.99 + return r diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcsgroupprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcsgroupprober.py new file mode 100644 index 0000000..98e95dc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sbcsgroupprober.py @@ -0,0 +1,73 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 .charsetgroupprober import CharSetGroupProber +from .sbcharsetprober import SingleByteCharSetProber +from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, + Latin5CyrillicModel, MacCyrillicModel, + Ibm866Model, Ibm855Model) +from .langgreekmodel import Latin7GreekModel, Win1253GreekModel +from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel +# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel +from .langthaimodel import TIS620ThaiModel +from .langhebrewmodel import Win1255HebrewModel +from .hebrewprober import HebrewProber +from .langturkishmodel import Latin5TurkishModel + + +class SBCSGroupProber(CharSetGroupProber): + def __init__(self): + super(SBCSGroupProber, self).__init__() + self.probers = [ + SingleByteCharSetProber(Win1251CyrillicModel), + SingleByteCharSetProber(Koi8rModel), + SingleByteCharSetProber(Latin5CyrillicModel), + SingleByteCharSetProber(MacCyrillicModel), + SingleByteCharSetProber(Ibm866Model), + SingleByteCharSetProber(Ibm855Model), + SingleByteCharSetProber(Latin7GreekModel), + SingleByteCharSetProber(Win1253GreekModel), + SingleByteCharSetProber(Latin5BulgarianModel), + SingleByteCharSetProber(Win1251BulgarianModel), + # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) + # after we retrain model. + # SingleByteCharSetProber(Latin2HungarianModel), + # SingleByteCharSetProber(Win1250HungarianModel), + SingleByteCharSetProber(TIS620ThaiModel), + SingleByteCharSetProber(Latin5TurkishModel), + ] + hebrew_prober = HebrewProber() + logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, + False, hebrew_prober) + visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, + hebrew_prober) + hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) + self.probers.extend([hebrew_prober, logical_hebrew_prober, + visual_hebrew_prober]) + + self.reset() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sjisprober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sjisprober.py new file mode 100644 index 0000000..9e29623 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/sjisprober.py @@ -0,0 +1,92 @@ +######################## 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): +# 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 ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import SJISDistributionAnalysis +from .jpcntx import SJISContextAnalysis +from .mbcssm import SJIS_SM_MODEL +from .enums import ProbingState, MachineState + + +class SJISProber(MultiByteCharSetProber): + def __init__(self): + super(SJISProber, self).__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() + self.context_analyzer.reset() + + @property + def charset_name(self): + return self.context_analyzer.charset_name + + @property + def language(self): + return "Japanese" + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + 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: + self._state = ProbingState.FOUND_IT + break + elif 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.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._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)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/universaldetector.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/universaldetector.py new file mode 100644 index 0000000..7b4e92d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/universaldetector.py @@ -0,0 +1,286 @@ +######################## BEGIN LICENSE BLOCK ######################## +# 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): +# 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 ######################### +""" +Module containing the UniversalDetector detector class, which is the primary +class a user of ``chardet`` should use. + +:author: Mark Pilgrim (initial port to Python) +:author: Shy Shalom (original C code) +:author: Dan Blanchard (major refactoring for 3.0) +:author: Ian Cordasco +""" + + +import codecs +import logging +import re + +from .charsetgroupprober import CharSetGroupProber +from .enums import InputState, LanguageFilter, ProbingState +from .escprober import EscCharSetProber +from .latin1prober import Latin1Prober +from .mbcsgroupprober import MBCSGroupProber +from .sbcsgroupprober import SBCSGroupProber + + +class UniversalDetector(object): + """ + The ``UniversalDetector`` class underlies the ``chardet.detect`` function + and coordinates all of the different charset probers. + + To get a ``dict`` containing an encoding and its confidence, you can simply + run: + + .. code:: + + u = UniversalDetector() + u.feed(some_bytes) + u.close() + detected = u.result + + """ + + 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 + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + self._has_win_bytes = None + self.reset() + + def reset(self): + """ + 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.done = False + self._got_data = False + self._has_win_bytes = False + self._input_state = InputState.PURE_ASCII + self._last_char = b'' + if self._esc_charset_prober: + self._esc_charset_prober.reset() + for prober in self._charset_probers: + prober.reset() + + def feed(self, byte_str): + """ + Takes a chunk of a document and feeds it through all of the relevant + charset probers. + + After calling ``feed``, you can check the value of the ``done`` + attribute to see if you need to continue feeding the + ``UniversalDetector`` more data, or if it has made a prediction + (in the ``result`` attribute). + + .. note:: + You should always call ``close`` when you're done feeding in your + document if ``done`` is not already ``True``. + """ + if self.done: + return + + if not len(byte_str): + return + + if not isinstance(byte_str, bytearray): + byte_str = bytearray(byte_str) + + # First check for known BOMs, since these are guaranteed to be correct + if not self._got_data: + # 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)): + # 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'): + # 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'): + # 00 00 FF FE UCS-4, unusual octet order BOM (2143) + self.result = {'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._got_data = True + if self.result['encoding'] is not None: + self.done = True + return + + # If none of those matched and we've only see ASCII so far, check + # for high bytes and escape sequences + 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): + self._input_state = InputState.ESC_ASCII + + self._last_char = byte_str[-1:] + + # 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 + # use such sequences. + if self._input_state == InputState.ESC_ASCII: + 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.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 + # single-byte probers that are left. The single-byte probers + # use character bigram distributions to determine the encoding, whereas + # the multi-byte probers use a combination of character unigram and + # bigram distributions. + elif self._input_state == InputState.HIGH_BYTE: + if not self._charset_probers: + self._charset_probers = [MBCSGroupProber(self.lang_filter)] + # If we're checking non-CJK encodings, use single-byte prober + if self.lang_filter & LanguageFilter.NON_CJK: + self._charset_probers.append(SBCSGroupProber()) + self._charset_probers.append(Latin1Prober()) + 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.done = True + break + if self.WIN_BYTE_DETECTOR.search(byte_str): + self._has_win_bytes = True + + def close(self): + """ + Stop analyzing the current document and come up with a final + prediction. + + :returns: The ``result`` attribute, a ``dict`` with the keys + `encoding`, `confidence`, and `language`. + """ + # Don't bother with checks if we're already done + if self.done: + return self.result + self.done = True + + if not self._got_data: + 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': ''} + + # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD + elif self._input_state == InputState.HIGH_BYTE: + prober_confidence = None + max_prober_confidence = 0.0 + max_prober = None + for prober in self._charset_probers: + if not prober: + continue + prober_confidence = prober.get_confidence() + if prober_confidence > max_prober_confidence: + max_prober_confidence = prober_confidence + 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() + 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 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} + + # 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') + 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()) + else: + self.logger.debug('%s %s confidence = %s', + prober.charset_name, + prober.language, + prober.get_confidence()) + return self.result diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/utf8prober.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/utf8prober.py new file mode 100644 index 0000000..6c3196c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/utf8prober.py @@ -0,0 +1,82 @@ +######################## 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): +# 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 ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState, MachineState +from .codingstatemachine import CodingStateMachine +from .mbcssm import UTF8_SM_MODEL + + + +class UTF8Prober(CharSetProber): + ONE_CHAR_PROB = 0.5 + + def __init__(self): + super(UTF8Prober, self).__init__() + self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) + self._num_mb_chars = None + self.reset() + + def reset(self): + super(UTF8Prober, self).reset() + self.coding_sm.reset() + self._num_mb_chars = 0 + + @property + def charset_name(self): + return "utf-8" + + @property + def language(self): + return "" + + def feed(self, byte_str): + 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: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + if self.coding_sm.get_current_charlen() >= 2: + self._num_mb_chars += 1 + + if self.state == ProbingState.DETECTING: + if self.get_confidence() > self.SHORTCUT_THRESHOLD: + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + unlike = 0.99 + if self._num_mb_chars < 6: + unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars + return 1.0 - unlike + else: + return unlike diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/version.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/version.py new file mode 100644 index 0000000..bb2a34a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/chardet/version.py @@ -0,0 +1,9 @@ +""" +This module exists only to simplify retrieving the version number of chardet +from within setup.py and from chardet subpackages. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + +__version__ = "3.0.4" +VERSION = __version__.split('.') diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/__init__.py new file mode 100644 index 0000000..2a3bf47 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/__init__.py @@ -0,0 +1,6 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit, colorama_text +from .ansi import Fore, Back, Style, Cursor +from .ansitowin32 import AnsiToWin32 + +__version__ = '0.4.1' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansi.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansi.py new file mode 100644 index 0000000..7877658 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansi.py @@ -0,0 +1,102 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +''' +This module generates ANSI character codes to printing colors to terminals. +See: http://en.wikipedia.org/wiki/ANSI_escape_code +''' + +CSI = '\033[' +OSC = '\033]' +BEL = '\007' + + +def code_to_chars(code): + return CSI + str(code) + 'm' + +def set_title(title): + return OSC + '2;' + title + BEL + +def clear_screen(mode=2): + return CSI + str(mode) + 'J' + +def clear_line(mode=2): + return CSI + str(mode) + 'K' + + +class AnsiCodes(object): + def __init__(self): + # the subclasses declare class attributes which are numbers. + # Upon instantiation we define instance attributes, which are the same + # as the class attributes but wrapped with the ANSI escape sequence + for name in dir(self): + if not name.startswith('_'): + value = getattr(self, name) + setattr(self, name, code_to_chars(value)) + + +class AnsiCursor(object): + def UP(self, n=1): + return CSI + str(n) + 'A' + def DOWN(self, n=1): + return CSI + str(n) + 'B' + def FORWARD(self, n=1): + return CSI + str(n) + 'C' + def BACK(self, n=1): + return CSI + str(n) + 'D' + def POS(self, x=1, y=1): + return CSI + str(y) + ';' + str(x) + 'H' + + +class AnsiFore(AnsiCodes): + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + RESET = 39 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 90 + LIGHTRED_EX = 91 + LIGHTGREEN_EX = 92 + LIGHTYELLOW_EX = 93 + LIGHTBLUE_EX = 94 + LIGHTMAGENTA_EX = 95 + LIGHTCYAN_EX = 96 + LIGHTWHITE_EX = 97 + + +class AnsiBack(AnsiCodes): + BLACK = 40 + RED = 41 + GREEN = 42 + YELLOW = 43 + BLUE = 44 + MAGENTA = 45 + CYAN = 46 + WHITE = 47 + RESET = 49 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 100 + LIGHTRED_EX = 101 + LIGHTGREEN_EX = 102 + LIGHTYELLOW_EX = 103 + LIGHTBLUE_EX = 104 + LIGHTMAGENTA_EX = 105 + LIGHTCYAN_EX = 106 + LIGHTWHITE_EX = 107 + + +class AnsiStyle(AnsiCodes): + BRIGHT = 1 + DIM = 2 + NORMAL = 22 + RESET_ALL = 0 + +Fore = AnsiFore() +Back = AnsiBack() +Style = AnsiStyle() +Cursor = AnsiCursor() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansitowin32.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansitowin32.py new file mode 100644 index 0000000..359c92b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/ansitowin32.py @@ -0,0 +1,257 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import re +import sys +import os + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style +from .winterm import WinTerm, WinColor, WinStyle +from .win32 import windll, winapi_test + + +winterm = None +if windll is not None: + winterm = WinTerm() + + +class StreamWrapper(object): + ''' + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()', which is delegated to our + Converter instance. + ''' + def __init__(self, wrapped, converter): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + self.__convertor = converter + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def __enter__(self, *args, **kwargs): + # special method lookup bypasses __getattr__/__getattribute__, see + # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit + # thus, contextlib magic methods are not proxied via __getattr__ + return self.__wrapped.__enter__(*args, **kwargs) + + def __exit__(self, *args, **kwargs): + return self.__wrapped.__exit__(*args, **kwargs) + + def write(self, text): + self.__convertor.write(text) + + def isatty(self): + stream = self.__wrapped + if 'PYCHARM_HOSTED' in os.environ: + if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__): + return True + try: + stream_isatty = stream.isatty + except AttributeError: + return False + else: + return stream_isatty() + + @property + def closed(self): + stream = self.__wrapped + try: + return stream.closed + except AttributeError: + return True + + +class AnsiToWin32(object): + ''' + Implements a 'write()' method which, on Windows, will strip ANSI character + sequences from the text, and if outputting to a tty, will convert them into + win32 function calls. + ''' + ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer + ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command + + def __init__(self, wrapped, convert=None, strip=None, autoreset=False): + # The wrapped stream (normally sys.stdout or sys.stderr) + self.wrapped = wrapped + + # should we reset colors to defaults after every .write() + self.autoreset = autoreset + + # create the proxy wrapping our output stream + self.stream = StreamWrapper(wrapped, self) + + on_windows = os.name == 'nt' + # We test if the WinAPI works, because even if we are on Windows + # we may be using a terminal that doesn't support the WinAPI + # (e.g. Cygwin Terminal). In this case it's up to the terminal + # to support the ANSI codes. + conversion_supported = on_windows and winapi_test() + + # should we strip ANSI sequences from our output? + if strip is None: + strip = conversion_supported or (not self.stream.closed and not self.stream.isatty()) + 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() + self.convert = convert + + # dict of ansi codes to win32 functions and parameters + self.win32_calls = self.get_win32_calls() + + # are we wrapping stderr? + self.on_stderr = self.wrapped is sys.stderr + + def should_wrap(self): + ''' + True if this class is actually needed. If false, then the output + stream will not be affected, nor will win32 calls be issued, so + wrapping stdout is not actually required. This will generally be + False on non-Windows platforms, unless optional functionality like + autoreset has been requested using kwargs to init() + ''' + return self.convert or self.strip or self.autoreset + + def get_win32_calls(self): + if self.convert and winterm: + return { + AnsiStyle.RESET_ALL: (winterm.reset_all, ), + AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), + AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), + AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), + AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), + AnsiFore.RED: (winterm.fore, WinColor.RED), + AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), + AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), + AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), + AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), + AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), + AnsiFore.WHITE: (winterm.fore, WinColor.GREY), + AnsiFore.RESET: (winterm.fore, ), + AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True), + AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True), + AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True), + AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True), + AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True), + AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True), + AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True), + AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True), + AnsiBack.BLACK: (winterm.back, WinColor.BLACK), + AnsiBack.RED: (winterm.back, WinColor.RED), + AnsiBack.GREEN: (winterm.back, WinColor.GREEN), + AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), + AnsiBack.BLUE: (winterm.back, WinColor.BLUE), + AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), + AnsiBack.CYAN: (winterm.back, WinColor.CYAN), + AnsiBack.WHITE: (winterm.back, WinColor.GREY), + AnsiBack.RESET: (winterm.back, ), + AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True), + AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True), + AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True), + AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True), + AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True), + AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True), + AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True), + AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True), + } + return dict() + + def write(self, text): + if self.strip or self.convert: + self.write_and_convert(text) + else: + self.wrapped.write(text) + self.wrapped.flush() + if self.autoreset: + self.reset_all() + + + def reset_all(self): + if self.convert: + self.call_win32('m', (0,)) + elif not self.strip and not self.stream.closed: + self.wrapped.write(Style.RESET_ALL) + + + def write_and_convert(self, text): + ''' + Write the given text to our wrapped stream, stripping any ANSI + sequences from the text, and optionally converting them into win32 + calls. + ''' + cursor = 0 + text = self.convert_osc(text) + for match in self.ANSI_CSI_RE.finditer(text): + start, end = match.span() + self.write_plain_text(text, cursor, start) + self.convert_ansi(*match.groups()) + cursor = end + self.write_plain_text(text, cursor, len(text)) + + + def write_plain_text(self, text, start, end): + if start < end: + self.wrapped.write(text[start:end]) + self.wrapped.flush() + + + def convert_ansi(self, paramstring, command): + if self.convert: + params = self.extract_params(command, paramstring) + self.call_win32(command, params) + + + def extract_params(self, command, paramstring): + if command in 'Hf': + params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';')) + while len(params) < 2: + # defaults: + params = params + (1,) + else: + params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0) + if len(params) == 0: + # defaults: + if command in 'JKm': + params = (0,) + elif command in 'ABCD': + params = (1,) + + return params + + + def call_win32(self, command, params): + if command == 'm': + for param in params: + if param in self.win32_calls: + func_args = self.win32_calls[param] + func = func_args[0] + args = func_args[1:] + kwargs = dict(on_stderr=self.on_stderr) + func(*args, **kwargs) + elif command in 'J': + winterm.erase_screen(params[0], on_stderr=self.on_stderr) + elif command in 'K': + winterm.erase_line(params[0], on_stderr=self.on_stderr) + elif command in 'Hf': # cursor position - absolute + winterm.set_cursor_position(params, on_stderr=self.on_stderr) + elif command in 'ABCD': # cursor position - relative + n = params[0] + # A - up, B - down, C - forward, D - back + x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command] + winterm.cursor_adjust(x, y, on_stderr=self.on_stderr) + + + def convert_osc(self, text): + for match in self.ANSI_OSC_RE.finditer(text): + start, end = match.span() + text = text[:start] + text[end:] + paramstring, command = match.groups() + if command in '\x07': # \x07 = BEL + params = paramstring.split(";") + # 0 - change title and icon (we will only change title) + # 1 - change icon (we don't support this) + # 2 - change title + if params[0] in '02': + winterm.set_title(params[1]) + return text diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/initialise.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/initialise.py new file mode 100644 index 0000000..430d066 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/initialise.py @@ -0,0 +1,80 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import atexit +import contextlib +import sys + +from .ansitowin32 import AnsiToWin32 + + +orig_stdout = None +orig_stderr = None + +wrapped_stdout = None +wrapped_stderr = None + +atexit_done = False + + +def reset_all(): + if AnsiToWin32 is not None: # Issue #74: objects might become None at exit + AnsiToWin32(orig_stdout).reset_all() + + +def init(autoreset=False, convert=None, strip=None, wrap=True): + + if not wrap and any([autoreset, convert, strip]): + raise ValueError('wrap=False conflicts with any other arg=True') + + global wrapped_stdout, wrapped_stderr + global orig_stdout, orig_stderr + + orig_stdout = sys.stdout + orig_stderr = sys.stderr + + if sys.stdout is None: + wrapped_stdout = None + else: + sys.stdout = wrapped_stdout = \ + wrap_stream(orig_stdout, convert, strip, autoreset, wrap) + if sys.stderr is None: + wrapped_stderr = None + else: + sys.stderr = wrapped_stderr = \ + wrap_stream(orig_stderr, convert, strip, autoreset, wrap) + + global atexit_done + if not atexit_done: + atexit.register(reset_all) + atexit_done = True + + +def deinit(): + if orig_stdout is not None: + sys.stdout = orig_stdout + if orig_stderr is not None: + sys.stderr = orig_stderr + + +@contextlib.contextmanager +def colorama_text(*args, **kwargs): + init(*args, **kwargs) + try: + yield + finally: + deinit() + + +def reinit(): + if wrapped_stdout is not None: + sys.stdout = wrapped_stdout + if wrapped_stderr is not None: + sys.stderr = wrapped_stderr + + +def wrap_stream(stream, convert, strip, autoreset, wrap): + if wrap: + wrapper = AnsiToWin32(stream, + convert=convert, strip=strip, autoreset=autoreset) + if wrapper.should_wrap(): + stream = wrapper.stream + return stream diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/win32.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/win32.py new file mode 100644 index 0000000..c2d8360 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/win32.py @@ -0,0 +1,152 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +try: + import ctypes + from ctypes import LibraryLoader + windll = LibraryLoader(ctypes.WinDLL) + from ctypes import wintypes +except (AttributeError, ImportError): + windll = None + SetConsoleTextAttribute = lambda *_: None + winapi_test = lambda *_: None +else: + from ctypes import byref, Structure, c_char, POINTER + + COORD = wintypes._COORD + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW + _SetConsoleTitleW.argtypes = [ + wintypes.LPCWSTR + ] + _SetConsoleTitleW.restype = wintypes.BOOL + + def _winapi_test(handle): + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return bool(success) + + def winapi_test(): + return any(_winapi_test(h) for h in + (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = _GetStdHandle(stream_id) + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = _GetStdHandle(stream_id) + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position, adjust=True): + position = COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = COORD(position.Y - 1, position.X - 1) + if adjust: + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = _GetStdHandle(stream_id) + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = _GetStdHandle(stream_id) + char = c_char(char.encode()) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = _GetStdHandle(stream_id) + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) + + def SetConsoleTitle(title): + return _SetConsoleTitleW(title) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/winterm.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/winterm.py new file mode 100644 index 0000000..0fdb4ec --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/colorama/winterm.py @@ -0,0 +1,169 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from . import win32 + + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + BRIGHT_BACKGROUND = 0x80 # dim text, bright background + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. + # So that LIGHT_EX colors and BRIGHT style do not clobber each other, + # we track them separately, since LIGHT_EX is overwritten by Fore/Back + # and BRIGHT is overwritten by Style codes. + self._light = 0 + + def get_attrs(self): + return self._fore + self._back * 16 + (self._style | self._light) + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + self._light = 0 + + def fore(self, fore=None, light=False, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + # Emulate LIGHT_EX with BRIGHT Style + if light: + self._light |= WinStyle.BRIGHT + else: + self._light &= ~WinStyle.BRIGHT + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, light=False, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style + if light: + self._light |= WinStyle.BRIGHT_BACKGROUND + else: + self._light &= ~WinStyle.BRIGHT_BACKGROUND + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + # I'm not currently tracking the position, so there is no default. + # position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_adjust(self, x, y, on_stderr=False): + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y + y, position.X + x) + win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) + + def erase_screen(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen, and move cursor to (1,1) + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y + # get number of character cells before current cursor position + cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = cells_in_screen - cells_before_cursor + elif mode == 1: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_before_cursor + elif mode == 2: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_in_screen + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + if mode == 2: + # put the cursor where needed + win32.SetConsoleCursorPosition(handle, (1, 1)) + + def erase_line(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the line. + # 1 should clear from the cursor to the beginning of the line. + # 2 should clear the entire line. + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X + elif mode == 1: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwCursorPosition.X + elif mode == 2: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwSize.X + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + + def set_title(self, title): + win32.SetConsoleTitle(title) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/__init__.py new file mode 100644 index 0000000..a786b4d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import logging + +__version__ = '0.2.8' + +class DistlibException(Exception): + pass + +try: + from logging import NullHandler +except ImportError: # pragma: no cover + class NullHandler(logging.Handler): + def handle(self, record): pass + def emit(self, record): pass + def createLock(self): self.lock = None + +logger = logging.getLogger(__name__) +logger.addHandler(NullHandler()) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/__init__.py new file mode 100644 index 0000000..f7dbf4c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/__init__.py @@ -0,0 +1,6 @@ +"""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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/misc.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/misc.py new file mode 100644 index 0000000..cfb318d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/misc.py @@ -0,0 +1,41 @@ +# -*- 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/shutil.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/shutil.py new file mode 100644 index 0000000..159e49e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/shutil.py @@ -0,0 +1,761 @@ +# -*- 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 +import collections +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, collections.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, collections.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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.cfg b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.cfg new file mode 100644 index 0000000..1746bd0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.cfg @@ -0,0 +1,84 @@ +[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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.py new file mode 100644 index 0000000..1df3aba --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/sysconfig.py @@ -0,0 +1,788 @@ +# -*- 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) + + # FIXME don't rely on sys.version here, its format is an implementation detail + # of CPython, use sys.version_info or sys.hexversion +_PY_VERSION = sys.version.split()[0] +_PY_VERSION_SHORT = sys.version[:3] +_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/tarfile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/tarfile.py new file mode 100644 index 0000000..d66d856 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/_backport/tarfile.py @@ -0,0 +1,2607 @@ +#------------------------------------------------------------------- +# 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/compat.py new file mode 100644 index 0000000..ff328c8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/compat.py @@ -0,0 +1,1120 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import absolute_import + +import os +import re +import sys + +try: + import ssl +except ImportError: # pragma: no cover + ssl = None + +if sys.version_info[0] < 3: # pragma: no cover + from StringIO import StringIO + string_types = basestring, + text_type = unicode + 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) + + def quote(s): + if isinstance(s, unicode): + s = s.encode('utf-8') + return _quote(s) + + import urllib2 + from urllib2 import (Request, urlopen, URLError, HTTPError, + HTTPBasicAuthHandler, HTTPPasswordMgr, + HTTPHandler, HTTPRedirectHandler, + build_opener) + if ssl: + from urllib2 import HTTPSHandler + import httplib + import xmlrpclib + import Queue as queue + from HTMLParser import HTMLParser + import htmlentitydefs + raw_input = raw_input + from itertools import ifilter as filter + from itertools import ifilterfalse as filterfalse + + _userprog = None + def splituser(host): + """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" + global _userprog + if _userprog is None: + import re + _userprog = re.compile('^(.*)@(.*)$') + + match = _userprog.match(host) + if match: return match.group(1, 2) + return None, host + +else: # pragma: no cover + from io import StringIO + string_types = str, + text_type = str + from io import TextIOWrapper as file_type + import builtins + import configparser + import shutil + from urllib.parse import (urlparse, urlunparse, urljoin, splituser, quote, + unquote, urlsplit, urlunsplit, splittype) + from urllib.request import (urlopen, urlretrieve, Request, url2pathname, + pathname2url, + HTTPBasicAuthHandler, HTTPPasswordMgr, + HTTPHandler, HTTPRedirectHandler, + build_opener) + if ssl: + from urllib.request import HTTPSHandler + from urllib.error import HTTPError, URLError, ContentTooShortError + import http.client as httplib + import urllib.request as urllib2 + import xmlrpc.client as xmlrpclib + import queue + from html.parser import HTMLParser + import html.entities as htmlentitydefs + raw_input = input + from itertools import filterfalse + filter = filter + +try: + from ssl import match_hostname, CertificateError +except ImportError: # pragma: no cover + class CertificateError(ValueError): + pass + + + def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + parts = dn.split('.') + leftmost, remainder = parts[0], parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + + def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") + + +try: + from types import SimpleNamespace as Container +except ImportError: # pragma: no cover + class Container(object): + """ + A generic container for when multiple values need to be returned + """ + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +try: + from shutil import which +except ImportError: # pragma: no cover + # Implementation from Python 3.3 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if not os.curdir in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +# ZipFile is a context manager in 2.7, but not in 2.6 + +from zipfile import ZipFile as BaseZipFile + +if hasattr(BaseZipFile, '__enter__'): # pragma: no cover + ZipFile = BaseZipFile +else: # pragma: no cover + from zipfile import ZipExtFile as BaseZipExtFile + + class ZipExtFile(BaseZipExtFile): + def __init__(self, base): + self.__dict__.update(base.__dict__) + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + # return None, so if an exception occurred, it will propagate + + class ZipFile(BaseZipFile): + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + # return None, so if an exception occurred, it will propagate + + def open(self, *args, **kwargs): + base = BaseZipFile.open(self, *args, **kwargs) + return ZipExtFile(base) + +try: + from platform import python_implementation +except ImportError: # pragma: no cover + def python_implementation(): + """Return a string identifying the Python implementation.""" + if 'PyPy' in sys.version: + return 'PyPy' + if os.name == 'java': + return 'Jython' + if sys.version.startswith('IronPython'): + return 'IronPython' + return 'CPython' + +try: + import sysconfig +except ImportError: # pragma: no cover + from ._backport import sysconfig + +try: + callable = callable +except NameError: # pragma: no cover + from collections import Callable + + def callable(obj): + return isinstance(obj, Callable) + + +try: + fsencode = os.fsencode + fsdecode = os.fsdecode +except AttributeError: # pragma: no cover + # Issue #99: on some systems (e.g. containerised), + # sys.getfilesystemencoding() returns None, and we need a real value, + # so fall back to utf-8. From the CPython 2.7 docs relating to Unix and + # sys.getfilesystemencoding(): the return value is "the user’s preference + # according to the result of nl_langinfo(CODESET), or None if the + # nl_langinfo(CODESET) failed." + _fsencoding = sys.getfilesystemencoding() or 'utf-8' + if _fsencoding == 'mbcs': + _fserrors = 'strict' + else: + _fserrors = 'surrogateescape' + + def fsencode(filename): + if isinstance(filename, bytes): + return filename + elif isinstance(filename, text_type): + return filename.encode(_fsencoding, _fserrors) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) + + def fsdecode(filename): + if isinstance(filename, text_type): + return filename + elif isinstance(filename, bytes): + return filename.decode(_fsencoding, _fserrors) + else: + raise TypeError("expect bytes or str, not %s" % + type(filename).__name__) + +try: + from tokenize import detect_encoding +except ImportError: # pragma: no cover + from codecs import BOM_UTF8, lookup + import re + + cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)") + + def _get_normal_name(orig_enc): + """Imitates get_normal_name in tokenizer.c.""" + # Only care about the first 12 characters. + enc = orig_enc[:12].lower().replace("_", "-") + if enc == "utf-8" or enc.startswith("utf-8-"): + return "utf-8" + if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \ + enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")): + return "iso-8859-1" + return orig_enc + + def detect_encoding(readline): + """ + The detect_encoding() function is used to detect the encoding that should + be used to decode a Python source file. It requires one argument, readline, + in the same way as the tokenize() generator. + + It will call readline a maximum of twice, and return the encoding used + (as a string) and a list of any lines (left as bytes) it has read in. + + It detects the encoding from the presence of a utf-8 bom or an encoding + cookie as specified in pep-0263. If both a bom and a cookie are present, + but disagree, a SyntaxError will be raised. If the encoding cookie is an + invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, + 'utf-8-sig' is returned. + + If no encoding is specified, then the default of 'utf-8' will be returned. + """ + try: + filename = readline.__self__.name + except AttributeError: + filename = None + bom_found = False + encoding = None + default = 'utf-8' + def read_or_stop(): + try: + return readline() + except StopIteration: + return b'' + + def find_cookie(line): + try: + # Decode as UTF-8. Either the line is an encoding declaration, + # in which case it should be pure ASCII, or it must be UTF-8 + # per default encoding. + line_string = line.decode('utf-8') + except UnicodeDecodeError: + msg = "invalid or missing encoding declaration" + if filename is not None: + msg = '{} for {!r}'.format(msg, filename) + raise SyntaxError(msg) + + matches = cookie_re.findall(line_string) + if not matches: + return None + encoding = _get_normal_name(matches[0]) + try: + codec = lookup(encoding) + except LookupError: + # This behaviour mimics the Python interpreter + if filename is None: + msg = "unknown encoding: " + encoding + else: + msg = "unknown encoding for {!r}: {}".format(filename, + encoding) + raise SyntaxError(msg) + + if bom_found: + if codec.name != 'utf-8': + # This behaviour mimics the Python interpreter + if filename is None: + msg = 'encoding problem: utf-8' + else: + msg = 'encoding problem for {!r}: utf-8'.format(filename) + raise SyntaxError(msg) + encoding += '-sig' + return encoding + + first = read_or_stop() + if first.startswith(BOM_UTF8): + bom_found = True + first = first[3:] + default = 'utf-8-sig' + if not first: + return default, [] + + encoding = find_cookie(first) + if encoding: + return encoding, [first] + + second = read_or_stop() + if not second: + return default, [first] + + encoding = find_cookie(second) + if encoding: + return encoding, [first, second] + + return default, [first, second] + +# For converting & <-> & etc. +try: + from html import escape +except ImportError: + from cgi import escape +if sys.version_info[:2] < (3, 4): + unescape = HTMLParser().unescape +else: + from html import unescape + +try: + from collections import ChainMap +except ImportError: # pragma: no cover + from collections import MutableMapping + + try: + from reprlib import recursive_repr as _recursive_repr + except ImportError: + def _recursive_repr(fillvalue='...'): + ''' + Decorator to make a repr function return fillvalue for a recursive + call + ''' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) + return wrapper + + return decorating_function + + class ChainMap(MutableMapping): + ''' A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + accessed or updated using the *maps* attribute. There is no other state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__(key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union(*self.maps)) # reuses stored hash values if possible + + def __iter__(self): + return iter(set().union(*self.maps)) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self): # like Django's Context.push() + 'New ChainMap with a new dict followed by all previous maps.' + return self.__class__({}, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() + +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 + +try: + from collections import OrderedDict +except ImportError: # pragma: no cover +## {{{ http://code.activestate.com/recipes/576693/ (r9) +# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. +# Passes Python2.7's test suite and incorporates all the latest updates. + try: + from thread import get_ident as _get_ident + except ImportError: + from dummy_thread import get_ident as _get_ident + + try: + from _abcoll import KeysView, ValuesView, ItemsView + except ImportError: + pass + + + class OrderedDict(dict): + 'Dictionary that remembers insertion order' + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as for regular dictionaries. + + # The internal self.__map dictionary maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # Each link is stored as a list of length three: [PREV, NEXT, KEY]. + + def __init__(self, *args, **kwds): + '''Initialize an ordered dictionary. Signature is the same as for + regular dictionaries, but keyword arguments are not recommended + because their insertion order is arbitrary. + + ''' + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__root + except AttributeError: + self.__root = root = [] # sentinel node + root[:] = [root, root, None] + self.__map = {} + self.__update(*args, **kwds) + + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link which goes at the end of the linked + # list, and the inherited dictionary is updated with the new key/value pair. + if key not in self: + root = self.__root + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which is + # then removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link_prev, link_next, key = self.__map.pop(key) + link_prev[1] = link_next + link_next[0] = link_prev + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + root = self.__root + curr = root[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + root = self.__root + curr = root[0] + while curr is not root: + yield curr[2] + curr = curr[0] + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + try: + for node in self.__map.itervalues(): + del node[:] + root = self.__root + root[:] = [root, root, None] + self.__map.clear() + except AttributeError: + pass + dict.clear(self) + + def popitem(self, last=True): + '''od.popitem() -> (k, v), return and remove a (key, value) pair. + Pairs are returned in LIFO order if last is true or FIFO order if false. + + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root[0] + link_prev = link[0] + link_prev[1] = root + root[0] = link_prev + else: + link = root[1] + link_next = link[1] + root[1] = link_next + link_next[0] = root + key = link[2] + del self.__map[key] + value = dict.pop(self, key) + return key, value + + # -- the following methods do not depend on the internal structure -- + + def keys(self): + 'od.keys() -> list of keys in od' + return list(self) + + def values(self): + 'od.values() -> list of values in od' + return [self[key] for key in self] + + def items(self): + 'od.items() -> list of (key, value) pairs in od' + return [(key, self[key]) for key in self] + + def iterkeys(self): + 'od.iterkeys() -> an iterator over the keys in od' + return iter(self) + + def itervalues(self): + 'od.itervalues -> an iterator over the values in od' + for k in self: + yield self[k] + + def iteritems(self): + 'od.iteritems -> an iterator over the (key, value) items in od' + for k in self: + yield (k, self[k]) + + def update(*args, **kwds): + '''od.update(E, **F) -> None. Update od from dict/iterable E and F. + + If E is a dict instance, does: for k in E: od[k] = E[k] + If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] + Or if E is an iterable of items, does: for k, v in E: od[k] = v + In either case, this is followed by: for k, v in F.items(): od[k] = v + + ''' + if len(args) > 2: + raise TypeError('update() takes at most 2 positional ' + 'arguments (%d given)' % (len(args),)) + elif not args: + raise TypeError('update() takes at least 1 argument (0 given)') + self = args[0] + # Make progressively weaker assumptions about "other" + other = () + if len(args) == 2: + other = args[1] + if isinstance(other, dict): + for key in other: + self[key] = other[key] + elif hasattr(other, 'keys'): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + __update = update # let subclasses override update without breaking __init__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' + if key in self: + return self[key] + self[key] = default + return default + + def __repr__(self, _repr_running=None): + 'od.__repr__() <==> repr(od)' + if not _repr_running: _repr_running = {} + call_key = id(self), _get_ident() + if call_key in _repr_running: + return '...' + _repr_running[call_key] = 1 + try: + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + finally: + del _repr_running[call_key] + + def __reduce__(self): + 'Return state information for pickling' + items = [[k, self[k]] for k in self] + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S + and values equal to v (which defaults to None). + + ''' + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return len(self)==len(other) and self.items() == other.items() + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + # -- the following methods are only used in Python 2.7 -- + + def viewkeys(self): + "od.viewkeys() -> a set-like object providing a view on od's keys" + return KeysView(self) + + def viewvalues(self): + "od.viewvalues() -> an object providing a view on od's values" + return ValuesView(self) + + def viewitems(self): + "od.viewitems() -> a set-like object providing a view on od's items" + return ItemsView(self) + +try: + from logging.config import BaseConfigurator, valid_ident +except ImportError: # pragma: no cover + IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I) + + + def valid_ident(s): + m = IDENTIFIER.match(s) + if not m: + raise ValueError('Not a valid Python identifier: %r' % s) + return True + + + # The ConvertingXXX classes are wrappers around standard Python containers, + # and they serve to convert any suitable values in the container. The + # conversion converts base dicts, lists and tuples to their wrapped + # equivalents, whereas strings which match a conversion format are converted + # appropriately. + # + # Each wrapper should have a configurator attribute holding the actual + # configurator to use for conversion. + + class ConvertingDict(dict): + """A converting dictionary wrapper.""" + + def __getitem__(self, key): + value = dict.__getitem__(self, key) + result = self.configurator.convert(value) + #If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def get(self, key, default=None): + value = dict.get(self, key, default) + result = self.configurator.convert(value) + #If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def pop(self, key, default=None): + value = dict.pop(self, key, default) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + class ConvertingList(list): + """A converting list wrapper.""" + def __getitem__(self, key): + value = list.__getitem__(self, key) + result = self.configurator.convert(value) + #If the converted value is different, save for next time + if value is not result: + self[key] = result + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + def pop(self, idx=-1): + value = list.pop(self, idx) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + return result + + class ConvertingTuple(tuple): + """A converting tuple wrapper.""" + def __getitem__(self, key): + value = tuple.__getitem__(self, key) + result = self.configurator.convert(value) + if value is not result: + if type(result) in (ConvertingDict, ConvertingList, + ConvertingTuple): + result.parent = self + result.key = key + return result + + class BaseConfigurator(object): + """ + The configurator base class which defines some useful defaults. + """ + + CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$') + + WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') + DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') + INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') + DIGIT_PATTERN = re.compile(r'^\d+$') + + value_converters = { + 'ext' : 'ext_convert', + 'cfg' : 'cfg_convert', + } + + # We might want to use a different one, e.g. importlib + importer = staticmethod(__import__) + + def __init__(self, config): + self.config = ConvertingDict(config) + self.config.configurator = self + + def resolve(self, s): + """ + Resolve strings to objects using standard import and attribute + syntax. + """ + name = s.split('.') + used = name.pop(0) + try: + found = self.importer(used) + for frag in name: + used += '.' + frag + try: + found = getattr(found, frag) + except AttributeError: + self.importer(used) + found = getattr(found, frag) + return found + except ImportError: + e, tb = sys.exc_info()[1:] + v = ValueError('Cannot resolve %r: %s' % (s, e)) + v.__cause__, v.__traceback__ = e, tb + raise v + + def ext_convert(self, value): + """Default converter for the ext:// protocol.""" + return self.resolve(value) + + def cfg_convert(self, value): + """Default converter for the cfg:// protocol.""" + rest = value + m = self.WORD_PATTERN.match(rest) + if m is None: + raise ValueError("Unable to convert %r" % value) + else: + rest = rest[m.end():] + d = self.config[m.groups()[0]] + #print d, rest + while rest: + m = self.DOT_PATTERN.match(rest) + if m: + d = d[m.groups()[0]] + else: + m = self.INDEX_PATTERN.match(rest) + if m: + idx = m.groups()[0] + if not self.DIGIT_PATTERN.match(idx): + d = d[idx] + else: + try: + n = int(idx) # try as number first (most likely) + d = d[n] + except TypeError: + d = d[idx] + if m: + rest = rest[m.end():] + else: + raise ValueError('Unable to convert ' + '%r at %r' % (value, rest)) + #rest should be empty + return d + + def convert(self, value): + """ + Convert values to an appropriate type. dicts, lists and tuples are + replaced by their converting alternatives. Strings are checked to + see if they have a conversion format and are converted if they do. + """ + if not isinstance(value, ConvertingDict) and isinstance(value, dict): + value = ConvertingDict(value) + value.configurator = self + elif not isinstance(value, ConvertingList) and isinstance(value, list): + value = ConvertingList(value) + value.configurator = self + elif not isinstance(value, ConvertingTuple) and\ + isinstance(value, tuple): + value = ConvertingTuple(value) + value.configurator = self + elif isinstance(value, string_types): + m = self.CONVERT_PATTERN.match(value) + if m: + d = m.groupdict() + prefix = d['prefix'] + converter = self.value_converters.get(prefix, None) + if converter: + suffix = d['suffix'] + converter = getattr(self, converter) + value = converter(suffix) + return value + + def configure_custom(self, config): + """Configure an object with a user-supplied factory.""" + c = config.pop('()') + if not callable(c): + c = self.resolve(c) + props = config.pop('.', None) + # Check for valid identifiers + kwargs = dict([(k, config[k]) for k in config if valid_ident(k)]) + result = c(**kwargs) + if props: + for name, value in props.items(): + setattr(result, name, value) + return result + + def as_tuple(self, value): + """Utility function which converts lists to tuples.""" + if isinstance(value, list): + value = tuple(value) + return value diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/database.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/database.py new file mode 100644 index 0000000..b13cdac --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/database.py @@ -0,0 +1,1339 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2017 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""PEP 376 implementation.""" + +from __future__ import unicode_literals + +import base64 +import codecs +import contextlib +import hashlib +import logging +import os +import posixpath +import sys +import zipimport + +from . import DistlibException, resources +from .compat import StringIO +from .version import get_scheme, UnsupportedVersionError +from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, + LEGACY_METADATA_FILENAME) +from .util import (parse_requirement, cached_property, parse_name_and_version, + read_exports, write_exports, CSVReader, CSVWriter) + + +__all__ = ['Distribution', 'BaseInstalledDistribution', + 'InstalledDistribution', 'EggInfoDistribution', + 'DistributionPath'] + + +logger = logging.getLogger(__name__) + +EXPORTS_FILENAME = 'pydist-exports.json' +COMMANDS_FILENAME = 'pydist-commands.json' + +DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', + 'RESOURCES', EXPORTS_FILENAME, 'SHARED') + +DISTINFO_EXT = '.dist-info' + + +class _Cache(object): + """ + A simple cache mapping names and .dist-info paths to distributions + """ + def __init__(self): + """ + Initialise an instance. There is normally one for each DistributionPath. + """ + self.name = {} + self.path = {} + self.generated = False + + def clear(self): + """ + Clear the cache, setting it to its initial state. + """ + self.name.clear() + self.path.clear() + self.generated = False + + def add(self, dist): + """ + Add a distribution to the cache. + :param dist: The distribution to add. + """ + if dist.path not in self.path: + self.path[dist.path] = dist + self.name.setdefault(dist.key, []).append(dist) + + +class DistributionPath(object): + """ + Represents a set of distributions installed on a path (typically sys.path). + """ + def __init__(self, path=None, include_egg=False): + """ + Create an instance from a path, optionally including legacy (distutils/ + setuptools/distribute) distributions. + :param path: The path to use, as a list of directories. If not specified, + sys.path is used. + :param include_egg: If True, this instance will look for and return legacy + distributions as well as those based on PEP 376. + """ + if path is None: + path = sys.path + self.path = path + self._include_dist = True + self._include_egg = include_egg + + self._cache = _Cache() + self._cache_egg = _Cache() + self._cache_enabled = True + self._scheme = get_scheme('default') + + def _get_cache_enabled(self): + return self._cache_enabled + + def _set_cache_enabled(self, value): + self._cache_enabled = value + + cache_enabled = property(_get_cache_enabled, _set_cache_enabled) + + def clear_cache(self): + """ + Clears the internal cache. + """ + self._cache.clear() + self._cache_egg.clear() + + + def _yield_distributions(self): + """ + Yield .dist-info and/or .egg(-info) distributions. + """ + # We need to check if we've seen some resources already, because on + # some Linux systems (e.g. some Debian/Ubuntu variants) there are + # symlinks which alias other files in the environment. + seen = set() + for path in self.path: + finder = resources.finder_for_path(path) + if finder is None: + continue + r = finder.find('') + if not r or not r.is_container: + continue + rset = sorted(r.resources) + for entry in rset: + 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 + + 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) + + def _generate_cache(self): + """ + Scan the path for distributions and populate the cache with + those that are found. + """ + gen_dist = not self._cache.generated + gen_egg = self._include_egg and not self._cache_egg.generated + if gen_dist or gen_egg: + for dist in self._yield_distributions(): + if isinstance(dist, InstalledDistribution): + self._cache.add(dist) + else: + self._cache_egg.add(dist) + + if gen_dist: + self._cache.generated = True + if gen_egg: + self._cache_egg.generated = True + + @classmethod + def distinfo_dirname(cls, name, version): + """ + The *name* and *version* parameters are converted into their + filename-escaped form, i.e. any ``'-'`` characters are replaced + with ``'_'`` other than the one in ``'dist-info'`` and the one + separating the name from the version number. + + :parameter name: is converted to a standard distribution name by replacing + any runs of non- alphanumeric characters with a single + ``'-'``. + :type name: string + :parameter version: is converted to a standard version string. Spaces + become dots, and all other non-alphanumeric characters + (except dots) become dashes, with runs of multiple + dashes condensed to a single dash. + :type version: string + :returns: directory name + :rtype: string""" + name = name.replace('-', '_') + return '-'.join([name, version]) + DISTINFO_EXT + + def get_distributions(self): + """ + Provides an iterator that looks for distributions and returns + :class:`InstalledDistribution` or + :class:`EggInfoDistribution` instances for each one of them. + + :rtype: iterator of :class:`InstalledDistribution` and + :class:`EggInfoDistribution` instances + """ + if not self._cache_enabled: + for dist in self._yield_distributions(): + yield dist + else: + self._generate_cache() + + for dist in self._cache.path.values(): + yield dist + + if self._include_egg: + for dist in self._cache_egg.path.values(): + yield dist + + def get_distribution(self, name): + """ + Looks for a named distribution on the path. + + This function only returns the first result found, as no more than one + value is expected. If nothing is found, ``None`` is returned. + + :rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution` + or ``None`` + """ + result = None + name = name.lower() + if not self._cache_enabled: + for dist in self._yield_distributions(): + if dist.key == name: + result = dist + break + else: + self._generate_cache() + + if name in self._cache.name: + result = self._cache.name[name][0] + elif self._include_egg and name in self._cache_egg.name: + result = self._cache_egg.name[name][0] + return result + + def provides_distribution(self, name, version=None): + """ + Iterates over all distributions to find which distributions provide *name*. + If a *version* is provided, it will be used to filter the results. + + This function only returns the first result found, since no more than + one values are expected. If the directory is not found, returns ``None``. + + :parameter version: a version specifier that indicates the version + required, conforming to the format in ``PEP-345`` + + :type name: string + :type version: string + """ + matcher = None + if version is not None: + try: + matcher = self._scheme.matcher('%s (%s)' % (name, version)) + except ValueError: + raise DistlibException('invalid name or version: %r, %r' % + (name, version)) + + for dist in self.get_distributions(): + # We hit a problem on Travis where enum34 was installed and doesn't + # have a provides attribute ... + if not hasattr(dist, 'provides'): + logger.debug('No "provides": %s', dist) + else: + provided = dist.provides + + for p in provided: + p_name, p_ver = parse_name_and_version(p) + if matcher is None: + if p_name == name: + yield dist + break + else: + if p_name == name and matcher.match(p_ver): + yield dist + break + + def get_file_path(self, name, relative_path): + """ + Return the path to a resource file. + """ + dist = self.get_distribution(name) + if dist is None: + raise LookupError('no distribution named %r found' % name) + return dist.get_resource_path(relative_path) + + def get_exported_entries(self, category, name=None): + """ + Return all of the exported entries in a particular category. + + :param category: The category to search for entries. + :param name: If specified, only entries with that name are returned. + """ + for dist in self.get_distributions(): + r = dist.exports + if category in r: + d = r[category] + if name is not None: + if name in d: + yield d[name] + else: + for v in d.values(): + yield v + + +class Distribution(object): + """ + A base class for distributions, whether installed or from indexes. + Either way, it must have some metadata, so that's all that's needed + for construction. + """ + + build_time_dependency = False + """ + Set to True if it's known to be only a build-time dependency (i.e. + not needed after installation). + """ + + requested = False + """A boolean that indicates whether the ``REQUESTED`` metadata file is + present (in other words, whether the package was installed by user + request or it was installed as a dependency).""" + + def __init__(self, metadata): + """ + Initialise an instance. + :param metadata: The instance of :class:`Metadata` describing this + distribution. + """ + self.metadata = metadata + self.name = metadata.name + self.key = self.name.lower() # for case-insensitive comparisons + self.version = metadata.version + self.locator = None + self.digest = None + self.extras = None # additional features requested + self.context = None # environment marker overrides + self.download_urls = set() + self.digests = {} + + @property + def source_url(self): + """ + The source archive download URL for this distribution. + """ + return self.metadata.source_url + + download_url = source_url # Backward compatibility + + @property + def name_and_version(self): + """ + A utility property which displays the name and version in parentheses. + """ + return '%s (%s)' % (self.name, self.version) + + @property + def provides(self): + """ + A set of distribution names and versions provided by this distribution. + :return: A set of "name (version)" strings. + """ + plist = self.metadata.provides + s = '%s (%s)' % (self.name, self.version) + if s not in plist: + plist.append(s) + return plist + + def _get_requirements(self, req_attr): + md = self.metadata + logger.debug('Getting requirements from metadata %r', md.todict()) + reqts = getattr(md, req_attr) + return set(md.get_requirements(reqts, extras=self.extras, + env=self.context)) + + @property + def run_requires(self): + return self._get_requirements('run_requires') + + @property + def meta_requires(self): + return self._get_requirements('meta_requires') + + @property + def build_requires(self): + return self._get_requirements('build_requires') + + @property + def test_requires(self): + return self._get_requirements('test_requires') + + @property + def dev_requires(self): + return self._get_requirements('dev_requires') + + def matches_requirement(self, req): + """ + Say if this instance matches (fulfills) a requirement. + :param req: The requirement to match. + :rtype req: str + :return: True if it matches, else False. + """ + # Requirement may contain extras - parse to lose those + # from what's passed to the matcher + r = parse_requirement(req) + scheme = get_scheme(self.metadata.scheme) + try: + matcher = scheme.matcher(r.requirement) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + logger.warning('could not read version %r - using name only', + req) + name = req.split()[0] + matcher = scheme.matcher(name) + + name = matcher.key # case-insensitive + + result = False + for p in self.provides: + p_name, p_ver = parse_name_and_version(p) + if p_name != name: + continue + try: + result = matcher.match(p_ver) + break + except UnsupportedVersionError: + pass + return result + + def __repr__(self): + """ + Return a textual representation of this instance, + """ + if self.source_url: + suffix = ' [%s]' % self.source_url + else: + suffix = '' + return '<Distribution %s (%s)%s>' % (self.name, self.version, suffix) + + def __eq__(self, other): + """ + See if this distribution is the same as another. + :param other: The distribution to compare with. To be equal to one + another. distributions must have the same type, name, + version and source_url. + :return: True if it is the same, else False. + """ + if type(other) is not type(self): + result = False + else: + result = (self.name == other.name and + self.version == other.version and + self.source_url == other.source_url) + return result + + def __hash__(self): + """ + Compute hash in a way which matches the equality test. + """ + return hash(self.name) + hash(self.version) + hash(self.source_url) + + +class BaseInstalledDistribution(Distribution): + """ + This is the base class for installed distributions (whether PEP 376 or + legacy). + """ + + hasher = None + + def __init__(self, metadata, path, env=None): + """ + Initialise an instance. + :param metadata: An instance of :class:`Metadata` which describes the + distribution. This will normally have been initialised + from a metadata file in the ``path``. + :param path: The path of the ``.dist-info`` or ``.egg-info`` + directory for the distribution. + :param env: This is normally the :class:`DistributionPath` + instance where this distribution was found. + """ + super(BaseInstalledDistribution, self).__init__(metadata) + self.path = path + self.dist_path = env + + def get_hash(self, data, hasher=None): + """ + Get the hash of some data, using a particular hash algorithm, if + specified. + + :param data: The data to be hashed. + :type data: bytes + :param hasher: The name of a hash implementation, supported by hashlib, + or ``None``. Examples of valid values are ``'sha1'``, + ``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and + ``'sha512'``. If no hasher is specified, the ``hasher`` + attribute of the :class:`InstalledDistribution` instance + is used. If the hasher is determined to be ``None``, MD5 + is used as the hashing algorithm. + :returns: The hash of the data. If a hasher was explicitly specified, + the returned hash will be prefixed with the specified hasher + followed by '='. + :rtype: str + """ + if hasher is None: + hasher = self.hasher + if hasher is None: + hasher = hashlib.md5 + prefix = '' + else: + hasher = getattr(hashlib, hasher) + prefix = '%s=' % self.hasher + digest = hasher(data).digest() + digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii') + return '%s%s' % (prefix, digest) + + +class InstalledDistribution(BaseInstalledDistribution): + """ + Created with the *path* of the ``.dist-info`` directory provided to the + constructor. It reads the metadata contained in ``pydist.json`` when it is + instantiated., or uses a passed in Metadata instance (useful for when + dry-run mode is being used). + """ + + hasher = 'sha256' + + def __init__(self, path, metadata=None, env=None): + self.modules = [] + self.finder = finder = resources.finder_for_path(path) + if finder is None: + raise ValueError('finder unavailable for %s' % path) + if env and env._cache_enabled and path in env._cache.path: + metadata = env._cache.path[path].metadata + elif metadata is None: + r = finder.find(METADATA_FILENAME) + # Temporary - for Wheel 0.23 support + if r is None: + r = finder.find(WHEEL_METADATA_FILENAME) + # Temporary - for legacy support + if r is None: + r = finder.find('METADATA') + if r is None: + raise ValueError('no %s found in %s' % (METADATA_FILENAME, + path)) + with contextlib.closing(r.as_stream()) as stream: + metadata = Metadata(fileobj=stream, scheme='legacy') + + super(InstalledDistribution, self).__init__(metadata, path, env) + + if env and env._cache_enabled: + env._cache.add(self) + + r = finder.find('REQUESTED') + self.requested = r is not None + p = os.path.join(path, 'top_level.txt') + if os.path.exists(p): + with open(p, 'rb') as f: + data = f.read() + self.modules = data.splitlines() + + def __repr__(self): + return '<InstalledDistribution %r %s at %r>' % ( + self.name, self.version, self.path) + + def __str__(self): + return "%s %s" % (self.name, self.version) + + def _get_records(self): + """ + Get the list of installed files for the distribution + :return: A list of tuples of path, hash and size. Note that hash and + size might be ``None`` for some entries. The path is exactly + as stored in the file (which is as in PEP 376). + """ + results = [] + r = self.get_distinfo_resource('RECORD') + with contextlib.closing(r.as_stream()) as stream: + with CSVReader(stream=stream) as record_reader: + # Base location is parent dir of .dist-info dir + #base_location = os.path.dirname(self.path) + #base_location = os.path.abspath(base_location) + for row in record_reader: + missing = [None for i in range(len(row), 3)] + path, checksum, size = row + missing + #if not os.path.isabs(path): + # path = path.replace('/', os.sep) + # path = os.path.join(base_location, path) + results.append((path, checksum, size)) + return results + + @cached_property + def exports(self): + """ + Return the information exported by this distribution. + :return: A dictionary of exports, mapping an export category to a dict + of :class:`ExportEntry` instances describing the individual + export entries, and keyed by name. + """ + result = {} + r = self.get_distinfo_resource(EXPORTS_FILENAME) + if r: + result = self.read_exports() + return result + + def read_exports(self): + """ + Read exports data from a file in .ini format. + + :return: A dictionary of exports, mapping an export category to a list + of :class:`ExportEntry` instances describing the individual + export entries. + """ + result = {} + r = self.get_distinfo_resource(EXPORTS_FILENAME) + if r: + with contextlib.closing(r.as_stream()) as stream: + result = read_exports(stream) + return result + + def write_exports(self, exports): + """ + Write a dictionary of exports to a file in .ini format. + :param exports: A dictionary of exports, mapping an export category to + a list of :class:`ExportEntry` instances describing the + individual export entries. + """ + rf = self.get_distinfo_file(EXPORTS_FILENAME) + with open(rf, 'w') as f: + write_exports(exports, f) + + def get_resource_path(self, relative_path): + """ + NOTE: This API may change in the future. + + Return the absolute path to a resource file with the given relative + path. + + :param relative_path: The path, relative to .dist-info, of the resource + of interest. + :return: The absolute path where the resource is to be found. + """ + r = self.get_distinfo_resource('RESOURCES') + with contextlib.closing(r.as_stream()) as stream: + with CSVReader(stream=stream) as resources_reader: + for relative, destination in resources_reader: + if relative == relative_path: + return destination + raise KeyError('no resource file with relative path %r ' + 'is installed' % relative_path) + + def list_installed_files(self): + """ + Iterates over the ``RECORD`` entries and returns a tuple + ``(path, hash, size)`` for each line. + + :returns: iterator of (path, hash, size) + """ + for result in self._get_records(): + yield result + + def write_installed_files(self, paths, prefix, dry_run=False): + """ + Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any + existing ``RECORD`` file is silently overwritten. + + prefix is used to determine when to write absolute paths. + """ + prefix = os.path.join(prefix, '') + base = os.path.dirname(self.path) + base_under_prefix = base.startswith(prefix) + base = os.path.join(base, '') + record_path = self.get_distinfo_file('RECORD') + logger.info('creating %s', record_path) + if dry_run: + return None + with CSVWriter(record_path) as writer: + for path in paths: + if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')): + # do not put size and hash, as in PEP-376 + hash_value = size = '' + else: + size = '%d' % os.path.getsize(path) + with open(path, 'rb') as fp: + hash_value = self.get_hash(fp.read()) + if path.startswith(base) or (base_under_prefix and + path.startswith(prefix)): + path = os.path.relpath(path, base) + writer.writerow((path, hash_value, size)) + + # add the RECORD file itself + if record_path.startswith(base): + record_path = os.path.relpath(record_path, base) + writer.writerow((record_path, '', '')) + return record_path + + def check_installed_files(self): + """ + Checks that the hashes and sizes of the files in ``RECORD`` are + matched by the files themselves. Returns a (possibly empty) list of + mismatches. Each entry in the mismatch list will be a tuple consisting + of the path, 'exists', 'size' or 'hash' according to what didn't match + (existence is checked first, then size, then hash), the expected + value and the actual value. + """ + mismatches = [] + base = os.path.dirname(self.path) + record_path = self.get_distinfo_file('RECORD') + for path, hash_value, size in self.list_installed_files(): + if not os.path.isabs(path): + path = os.path.join(base, path) + if path == record_path: + continue + if not os.path.exists(path): + mismatches.append((path, 'exists', True, False)) + elif os.path.isfile(path): + actual_size = str(os.path.getsize(path)) + if size and actual_size != size: + mismatches.append((path, 'size', size, actual_size)) + elif hash_value: + if '=' in hash_value: + hasher = hash_value.split('=', 1)[0] + else: + hasher = None + + with open(path, 'rb') as f: + actual_hash = self.get_hash(f.read(), hasher) + if actual_hash != hash_value: + mismatches.append((path, 'hash', hash_value, actual_hash)) + return mismatches + + @cached_property + def shared_locations(self): + """ + A dictionary of shared locations whose keys are in the set 'prefix', + 'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'. + The corresponding value is the absolute path of that category for + this distribution, and takes into account any paths selected by the + user at installation time (e.g. via command-line arguments). In the + case of the 'namespace' key, this would be a list of absolute paths + for the roots of namespace packages in this distribution. + + The first time this property is accessed, the relevant information is + read from the SHARED file in the .dist-info directory. + """ + result = {} + shared_path = os.path.join(self.path, 'SHARED') + if os.path.isfile(shared_path): + with codecs.open(shared_path, 'r', encoding='utf-8') as f: + lines = f.read().splitlines() + for line in lines: + key, value = line.split('=', 1) + if key == 'namespace': + result.setdefault(key, []).append(value) + else: + result[key] = value + return result + + def write_shared_locations(self, paths, dry_run=False): + """ + Write shared location information to the SHARED file in .dist-info. + :param paths: A dictionary as described in the documentation for + :meth:`shared_locations`. + :param dry_run: If True, the action is logged but no file is actually + written. + :return: The path of the file written to. + """ + shared_path = os.path.join(self.path, 'SHARED') + logger.info('creating %s', shared_path) + if dry_run: + return None + lines = [] + for key in ('prefix', 'lib', 'headers', 'scripts', 'data'): + path = paths[key] + if os.path.isdir(paths[key]): + lines.append('%s=%s' % (key, path)) + for ns in paths.get('namespace', ()): + lines.append('namespace=%s' % ns) + + with codecs.open(shared_path, 'w', encoding='utf-8') as f: + f.write('\n'.join(lines)) + return shared_path + + def get_distinfo_resource(self, path): + if path not in DIST_FILES: + raise DistlibException('invalid path for a dist-info file: ' + '%r at %r' % (path, self.path)) + finder = resources.finder_for_path(self.path) + if finder is None: + raise DistlibException('Unable to get a finder for %s' % self.path) + return finder.find(path) + + def get_distinfo_file(self, path): + """ + Returns a path located under the ``.dist-info`` directory. Returns a + string representing the path. + + :parameter path: a ``'/'``-separated path relative to the + ``.dist-info`` directory or an absolute path; + If *path* is an absolute path and doesn't start + with the ``.dist-info`` directory path, + a :class:`DistlibException` is raised + :type path: str + :rtype: str + """ + # Check if it is an absolute path # XXX use relpath, add tests + if path.find(os.sep) >= 0: + # it's an absolute path? + distinfo_dirname, path = path.split(os.sep)[-2:] + if distinfo_dirname != self.path.split(os.sep)[-1]: + raise DistlibException( + 'dist-info file %r does not belong to the %r %s ' + 'distribution' % (path, self.name, self.version)) + + # The file must be relative + if path not in DIST_FILES: + raise DistlibException('invalid path for a dist-info file: ' + '%r at %r' % (path, self.path)) + + return os.path.join(self.path, path) + + def list_distinfo_files(self): + """ + Iterates over the ``RECORD`` entries and returns paths for each line if + the path is pointing to a file located in the ``.dist-info`` directory + or one of its subdirectories. + + :returns: iterator of paths + """ + base = os.path.dirname(self.path) + for path, checksum, size in self._get_records(): + # XXX add separator or use real relpath algo + if not os.path.isabs(path): + path = os.path.join(base, path) + if path.startswith(self.path): + yield path + + def __eq__(self, other): + return (isinstance(other, InstalledDistribution) and + self.path == other.path) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + + +class EggInfoDistribution(BaseInstalledDistribution): + """Created with the *path* of the ``.egg-info`` directory or file provided + to the constructor. It reads the metadata contained in the file itself, or + if the given path happens to be a directory, the metadata is read from the + file ``PKG-INFO`` under that directory.""" + + requested = True # as we have no way of knowing, assume it was + shared_locations = {} + + def __init__(self, path, env=None): + def set_name_and_version(s, n, v): + s.name = n + s.key = n.lower() # for case-insensitive comparisons + s.version = v + + self.path = path + self.dist_path = env + if env and env._cache_enabled and path in env._cache_egg.path: + metadata = env._cache_egg.path[path].metadata + set_name_and_version(self, metadata.name, metadata.version) + else: + metadata = self._get_metadata(path) + + # Need to be set before caching + set_name_and_version(self, metadata.name, metadata.version) + + if env and env._cache_enabled: + env._cache_egg.add(self) + super(EggInfoDistribution, self).__init__(metadata, path, env) + + def _get_metadata(self, path): + requires = None + + def parse_requires_data(data): + """Create a list of dependencies from a requires.txt file. + + *data*: the contents of a setuptools-produced requires.txt file. + """ + reqs = [] + lines = data.splitlines() + for line in lines: + line = line.strip() + if line.startswith('['): + logger.warning('Unexpected line: quitting requirement scan: %r', + line) + break + r = parse_requirement(line) + if not r: + logger.warning('Not recognised as a requirement: %r', line) + continue + if r.extras: + logger.warning('extra requirements in requires.txt are ' + 'not supported') + if not r.constraints: + reqs.append(r.name) + else: + cons = ', '.join('%s%s' % c for c in r.constraints) + reqs.append('%s (%s)' % (r.name, cons)) + return reqs + + def parse_requires_path(req_path): + """Create a list of dependencies from a requires.txt file. + + *req_path*: the path to a setuptools-produced requires.txt file. + """ + + reqs = [] + try: + with codecs.open(req_path, 'r', 'utf-8') as fp: + reqs = parse_requires_data(fp.read()) + except IOError: + pass + return reqs + + tl_path = tl_data = None + if path.endswith('.egg'): + if os.path.isdir(path): + p = os.path.join(path, 'EGG-INFO') + meta_path = os.path.join(p, 'PKG-INFO') + metadata = Metadata(path=meta_path, scheme='legacy') + req_path = os.path.join(p, 'requires.txt') + tl_path = os.path.join(p, 'top_level.txt') + requires = parse_requires_path(req_path) + else: + # FIXME handle the case where zipfile is not available + zipf = zipimport.zipimporter(path) + fileobj = StringIO( + zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8')) + metadata = Metadata(fileobj=fileobj, scheme='legacy') + try: + data = zipf.get_data('EGG-INFO/requires.txt') + tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8') + requires = parse_requires_data(data.decode('utf-8')) + except IOError: + requires = None + elif path.endswith('.egg-info'): + if os.path.isdir(path): + req_path = os.path.join(path, 'requires.txt') + requires = parse_requires_path(req_path) + path = os.path.join(path, 'PKG-INFO') + tl_path = os.path.join(path, 'top_level.txt') + metadata = Metadata(path=path, scheme='legacy') + else: + raise DistlibException('path must end with .egg-info or .egg, ' + 'got %r' % path) + + if requires: + metadata.add_requirements(requires) + # look for top-level modules in top_level.txt, if present + if tl_data is None: + if tl_path is not None and os.path.exists(tl_path): + with open(tl_path, 'rb') as f: + tl_data = f.read().decode('utf-8') + if not tl_data: + tl_data = [] + else: + tl_data = tl_data.splitlines() + self.modules = tl_data + return metadata + + def __repr__(self): + return '<EggInfoDistribution %r %s at %r>' % ( + self.name, self.version, self.path) + + def __str__(self): + return "%s %s" % (self.name, self.version) + + def check_installed_files(self): + """ + Checks that the hashes and sizes of the files in ``RECORD`` are + matched by the files themselves. Returns a (possibly empty) list of + mismatches. Each entry in the mismatch list will be a tuple consisting + of the path, 'exists', 'size' or 'hash' according to what didn't match + (existence is checked first, then size, then hash), the expected + value and the actual value. + """ + mismatches = [] + record_path = os.path.join(self.path, 'installed-files.txt') + if os.path.exists(record_path): + for path, _, _ in self.list_installed_files(): + if path == record_path: + continue + if not os.path.exists(path): + mismatches.append((path, 'exists', True, False)) + return mismatches + + def list_installed_files(self): + """ + Iterates over the ``installed-files.txt`` entries and returns a tuple + ``(path, hash, size)`` for each line. + + :returns: a list of (path, hash, size) + """ + + def _md5(path): + f = open(path, 'rb') + try: + content = f.read() + finally: + f.close() + return hashlib.md5(content).hexdigest() + + def _size(path): + return os.stat(path).st_size + + record_path = os.path.join(self.path, 'installed-files.txt') + result = [] + if os.path.exists(record_path): + with codecs.open(record_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + p = os.path.normpath(os.path.join(self.path, line)) + # "./" is present as a marker between installed files + # and installation metadata files + if not os.path.exists(p): + logger.warning('Non-existent file: %s', p) + if p.endswith(('.pyc', '.pyo')): + continue + #otherwise fall through and fail + if not os.path.isdir(p): + result.append((p, _md5(p), _size(p))) + result.append((record_path, None, None)) + return result + + def list_distinfo_files(self, absolute=False): + """ + Iterates over the ``installed-files.txt`` entries and returns paths for + each line if the path is pointing to a file located in the + ``.egg-info`` directory or one of its subdirectories. + + :parameter absolute: If *absolute* is ``True``, each returned path is + transformed into a local absolute path. Otherwise the + raw value from ``installed-files.txt`` is returned. + :type absolute: boolean + :returns: iterator of paths + """ + record_path = os.path.join(self.path, 'installed-files.txt') + if os.path.exists(record_path): + skip = True + with codecs.open(record_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line == './': + skip = False + continue + if not skip: + p = os.path.normpath(os.path.join(self.path, line)) + if p.startswith(self.path): + if absolute: + yield p + else: + yield line + + def __eq__(self, other): + return (isinstance(other, EggInfoDistribution) and + self.path == other.path) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + __hash__ = object.__hash__ + +new_dist_class = InstalledDistribution +old_dist_class = EggInfoDistribution + + +class DependencyGraph(object): + """ + Represents a dependency graph between distributions. + + The dependency relationships are stored in an ``adjacency_list`` that maps + distributions to a list of ``(other, label)`` tuples where ``other`` + is a distribution and the edge is labeled with ``label`` (i.e. the version + specifier, if such was provided). Also, for more efficient traversal, for + every distribution ``x``, a list of predecessors is kept in + ``reverse_list[x]``. An edge from distribution ``a`` to + distribution ``b`` means that ``a`` depends on ``b``. If any missing + dependencies are found, they are stored in ``missing``, which is a + dictionary that maps distributions to a list of requirements that were not + provided by any other distributions. + """ + + def __init__(self): + self.adjacency_list = {} + self.reverse_list = {} + self.missing = {} + + def add_distribution(self, distribution): + """Add the *distribution* to the graph. + + :type distribution: :class:`distutils2.database.InstalledDistribution` + or :class:`distutils2.database.EggInfoDistribution` + """ + self.adjacency_list[distribution] = [] + self.reverse_list[distribution] = [] + #self.missing[distribution] = [] + + def add_edge(self, x, y, label=None): + """Add an edge from distribution *x* to distribution *y* with the given + *label*. + + :type x: :class:`distutils2.database.InstalledDistribution` or + :class:`distutils2.database.EggInfoDistribution` + :type y: :class:`distutils2.database.InstalledDistribution` or + :class:`distutils2.database.EggInfoDistribution` + :type label: ``str`` or ``None`` + """ + self.adjacency_list[x].append((y, label)) + # multiple edges are allowed, so be careful + if x not in self.reverse_list[y]: + self.reverse_list[y].append(x) + + def add_missing(self, distribution, requirement): + """ + Add a missing *requirement* for the given *distribution*. + + :type distribution: :class:`distutils2.database.InstalledDistribution` + or :class:`distutils2.database.EggInfoDistribution` + :type requirement: ``str`` + """ + logger.debug('%s missing %r', distribution, requirement) + self.missing.setdefault(distribution, []).append(requirement) + + def _repr_dist(self, dist): + return '%s %s' % (dist.name, dist.version) + + def repr_node(self, dist, level=1): + """Prints only a subgraph""" + output = [self._repr_dist(dist)] + for other, label in self.adjacency_list[dist]: + dist = self._repr_dist(other) + if label is not None: + dist = '%s [%s]' % (dist, label) + output.append(' ' * level + str(dist)) + suboutput = self.repr_node(other, level + 1) + subs = suboutput.split('\n') + output.extend(subs[1:]) + return '\n'.join(output) + + def to_dot(self, f, skip_disconnected=True): + """Writes a DOT output for the graph to the provided file *f*. + + If *skip_disconnected* is set to ``True``, then all distributions + that are not dependent on any other distribution are skipped. + + :type f: has to support ``file``-like operations + :type skip_disconnected: ``bool`` + """ + disconnected = [] + + f.write("digraph dependencies {\n") + for dist, adjs in self.adjacency_list.items(): + if len(adjs) == 0 and not skip_disconnected: + disconnected.append(dist) + for other, label in adjs: + if not label is None: + f.write('"%s" -> "%s" [label="%s"]\n' % + (dist.name, other.name, label)) + else: + f.write('"%s" -> "%s"\n' % (dist.name, other.name)) + if not skip_disconnected and len(disconnected) > 0: + f.write('subgraph disconnected {\n') + f.write('label = "Disconnected"\n') + f.write('bgcolor = red\n') + + for dist in disconnected: + f.write('"%s"' % dist.name) + f.write('\n') + f.write('}\n') + f.write('}\n') + + def topological_sort(self): + """ + Perform a topological sort of the graph. + :return: A tuple, the first element of which is a topologically sorted + list of distributions, and the second element of which is a + list of distributions that cannot be sorted because they have + circular dependencies and so form a cycle. + """ + result = [] + # Make a shallow copy of the adjacency list + alist = {} + for k, v in self.adjacency_list.items(): + alist[k] = v[:] + while True: + # See what we can remove in this run + to_remove = [] + for k, v in list(alist.items())[:]: + if not v: + to_remove.append(k) + del alist[k] + if not to_remove: + # What's left in alist (if anything) is a cycle. + break + # Remove from the adjacency list of others + for k, v in alist.items(): + alist[k] = [(d, r) for d, r in v if d not in to_remove] + logger.debug('Moving to result: %s', + ['%s (%s)' % (d.name, d.version) for d in to_remove]) + result.extend(to_remove) + return result, list(alist.keys()) + + def __repr__(self): + """Representation of the graph""" + output = [] + for dist, adjs in self.adjacency_list.items(): + output.append(self.repr_node(dist)) + return '\n'.join(output) + + +def make_graph(dists, scheme='default'): + """Makes a dependency graph from the given distributions. + + :parameter dists: a list of distributions + :type dists: list of :class:`distutils2.database.InstalledDistribution` and + :class:`distutils2.database.EggInfoDistribution` instances + :rtype: a :class:`DependencyGraph` instance + """ + scheme = get_scheme(scheme) + graph = DependencyGraph() + provided = {} # maps names to lists of (version, dist) tuples + + # first, build the graph and find out what's provided + for dist in dists: + graph.add_distribution(dist) + + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Add to provided: %s, %s, %s', name, version, dist) + provided.setdefault(name, []).append((version, dist)) + + # now make the edges + for dist in dists: + requires = (dist.run_requires | dist.meta_requires | + dist.build_requires | dist.dev_requires) + for req in requires: + try: + matcher = scheme.matcher(req) + except UnsupportedVersionError: + # XXX compat-mode if cannot read the version + logger.warning('could not read version %r - using name only', + req) + name = req.split()[0] + matcher = scheme.matcher(name) + + name = matcher.key # case-insensitive + + matched = False + if name in provided: + for version, provider in provided[name]: + try: + match = matcher.match(version) + except UnsupportedVersionError: + match = False + + if match: + graph.add_edge(dist, provider, req) + matched = True + break + if not matched: + graph.add_missing(dist, req) + return graph + + +def get_dependent_dists(dists, dist): + """Recursively generate a list of distributions from *dists* that are + dependent on *dist*. + + :param dists: a list of distributions + :param dist: a distribution, member of *dists* for which we are interested + """ + if dist not in dists: + raise DistlibException('given distribution %r is not a member ' + 'of the list' % dist.name) + graph = make_graph(dists) + + dep = [dist] # dependent distributions + todo = graph.reverse_list[dist] # list of nodes we should inspect + + while todo: + d = todo.pop() + dep.append(d) + for succ in graph.reverse_list[d]: + if succ not in dep: + todo.append(succ) + + dep.pop(0) # remove dist from dep, was there to prevent infinite loops + return dep + + +def get_required_dists(dists, dist): + """Recursively generate a list of distributions from *dists* that are + required by *dist*. + + :param dists: a list of distributions + :param dist: a distribution, member of *dists* for which we are interested + """ + 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 + todo = graph.adjacency_list[dist] # list of nodes we should inspect + + while todo: + d = todo.pop()[0] + req.append(d) + for pred in graph.adjacency_list[d]: + if pred not in req: + todo.append(pred) + + return req + + +def make_dist(name, version, **kwargs): + """ + A convenience method for making a dist given just a name and version. + """ + summary = kwargs.pop('summary', 'Placeholder for summary') + md = Metadata(**kwargs) + md.name = name + md.version = version + md.summary = summary or 'Placeholder for summary' + return Distribution(md) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/index.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/index.py new file mode 100644 index 0000000..2406be2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/index.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import hashlib +import logging +import os +import shutil +import subprocess +import tempfile +try: + from threading import Thread +except ImportError: + from dummy_threading import Thread + +from . import DistlibException +from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr, + urlparse, build_opener, string_types) +from .util import cached_property, zip_dir, ServerProxy + +logger = logging.getLogger(__name__) + +DEFAULT_INDEX = 'https://pypi.python.org/pypi' +DEFAULT_REALM = 'pypi' + +class PackageIndex(object): + """ + This class represents a package index compatible with PyPI, the Python + Package Index. + """ + + boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$' + + def __init__(self, url=None): + """ + Initialise an instance. + + :param url: The URL of the index. If not specified, the URL for PyPI is + used. + """ + self.url = url or DEFAULT_INDEX + self.read_configuration() + scheme, netloc, path, params, query, frag = urlparse(self.url) + if params or query or frag or scheme not in ('http', 'https'): + raise DistlibException('invalid repository: %s' % self.url) + self.password_handler = None + self.ssl_verifier = None + self.gpg = None + self.gpg_home = None + with open(os.devnull, 'w') as sink: + # Use gpg by default rather than gpg2, as gpg2 insists on + # prompting for passwords + for s in ('gpg', 'gpg2'): + try: + rc = subprocess.check_call([s, '--version'], stdout=sink, + stderr=sink) + if rc == 0: + self.gpg = s + break + except OSError: + pass + + def _get_pypirc_command(self): + """ + Get the distutils command for interacting with PyPI configurations. + :return: the command. + """ + from distutils.core import Distribution + from distutils.config import PyPIRCCommand + d = Distribution() + return PyPIRCCommand(d) + + def read_configuration(self): + """ + Read the PyPI access configuration as supported by distutils, getting + PyPI to do the actual work. This populates ``username``, ``password``, + ``realm`` and ``url`` attributes from the configuration. + """ + # get distutils to do the work + c = self._get_pypirc_command() + c.repository = self.url + cfg = c._read_pypirc() + self.username = cfg.get('username') + self.password = cfg.get('password') + self.realm = cfg.get('realm', 'pypi') + self.url = cfg.get('repository', self.url) + + def save_configuration(self): + """ + Save the PyPI access configuration. You must have set ``username`` and + ``password`` attributes before calling this method. + + Again, distutils is used to do the actual work. + """ + self.check_credentials() + # get distutils to do the work + c = self._get_pypirc_command() + c._store_pypirc(self.username, self.password) + + def check_credentials(self): + """ + Check that ``username`` and ``password`` have been set, and raise an + exception if not. + """ + if self.username is None or self.password is None: + raise DistlibException('username and password must be set') + pm = HTTPPasswordMgr() + _, netloc, _, _, _, _ = urlparse(self.url) + pm.add_password(self.realm, netloc, self.username, self.password) + self.password_handler = HTTPBasicAuthHandler(pm) + + def register(self, metadata): + """ + Register a distribution on PyPI, using the provided metadata. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the distribution to be + registered. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + metadata.validate() + d = metadata.todict() + d[':action'] = 'verify' + request = self.encode_request(d.items(), []) + response = self.send_request(request) + d[':action'] = 'submit' + request = self.encode_request(d.items(), []) + return self.send_request(request) + + def _reader(self, name, stream, outbuf): + """ + Thread runner for reading lines of from a subprocess into a buffer. + + :param name: The logical name of the stream (used for logging only). + :param stream: The stream to read from. This will typically a pipe + connected to the output stream of a subprocess. + :param outbuf: The list to append the read lines to. + """ + while True: + s = stream.readline() + if not s: + break + s = s.decode('utf-8').rstrip() + outbuf.append(s) + logger.debug('%s: %s' % (name, s)) + stream.close() + + def get_sign_command(self, filename, signer, sign_password, + keystore=None): + """ + Return a suitable command for signing a file. + + :param filename: The pathname to the file to be signed. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: The signing command as a list suitable to be + passed to :class:`subprocess.Popen`. + """ + cmd = [self.gpg, '--status-fd', '2', '--no-tty'] + if keystore is None: + keystore = self.gpg_home + if keystore: + cmd.extend(['--homedir', keystore]) + if sign_password is not None: + cmd.extend(['--batch', '--passphrase-fd', '0']) + td = tempfile.mkdtemp() + sf = os.path.join(td, os.path.basename(filename) + '.asc') + cmd.extend(['--detach-sign', '--armor', '--local-user', + signer, '--output', sf, filename]) + logger.debug('invoking: %s', ' '.join(cmd)) + return cmd, sf + + def run_command(self, cmd, input_data=None): + """ + Run a command in a child process , passing it any input data specified. + + :param cmd: The command to run. + :param input_data: If specified, this must be a byte string containing + data to be sent to the child process. + :return: A tuple consisting of the subprocess' exit code, a list of + lines read from the subprocess' ``stdout``, and a list of + lines read from the subprocess' ``stderr``. + """ + kwargs = { + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE, + } + if input_data is not None: + kwargs['stdin'] = subprocess.PIPE + stdout = [] + stderr = [] + p = subprocess.Popen(cmd, **kwargs) + # We don't use communicate() here because we may need to + # get clever with interacting with the command + t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout)) + t1.start() + t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr)) + t2.start() + if input_data is not None: + p.stdin.write(input_data) + p.stdin.close() + + p.wait() + t1.join() + t2.join() + return p.returncode, stdout, stderr + + def sign_file(self, filename, signer, sign_password, keystore=None): + """ + Sign a file. + + :param filename: The pathname to the file to be signed. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param keystore: The path to a directory which contains the keys + used in signing. If not specified, the instance's + ``gpg_home`` attribute is used instead. + :return: The absolute pathname of the file where the signature is + stored. + """ + cmd, sig_file = self.get_sign_command(filename, signer, sign_password, + keystore) + rc, stdout, stderr = self.run_command(cmd, + sign_password.encode('utf-8')) + if rc != 0: + raise DistlibException('sign command failed with error ' + 'code %s' % rc) + return sig_file + + def upload_file(self, metadata, filename, signer=None, sign_password=None, + filetype='sdist', pyversion='source', keystore=None): + """ + Upload a release file to the index. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the file to be uploaded. + :param filename: The pathname of the file to be uploaded. + :param signer: The identifier of the signer of the file. + :param sign_password: The passphrase for the signer's + private key used for signing. + :param filetype: The type of the file being uploaded. This is the + distutils command which produced that file, e.g. + ``sdist`` or ``bdist_wheel``. + :param pyversion: The version of Python which the release relates + to. For code compatible with any Python, this would + be ``source``, otherwise it would be e.g. ``3.2``. + :param keystore: The path to a directory which contains the keys + used in signing. If not specified, the instance's + ``gpg_home`` attribute is used instead. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + if not os.path.exists(filename): + raise DistlibException('not found: %s' % filename) + metadata.validate() + d = metadata.todict() + sig_file = None + if signer: + if not self.gpg: + logger.warning('no signing program available - not signed') + else: + sig_file = self.sign_file(filename, signer, sign_password, + keystore) + with open(filename, 'rb') as f: + file_data = f.read() + md5_digest = hashlib.md5(file_data).hexdigest() + sha256_digest = hashlib.sha256(file_data).hexdigest() + d.update({ + ':action': 'file_upload', + 'protocol_version': '1', + 'filetype': filetype, + 'pyversion': pyversion, + 'md5_digest': md5_digest, + 'sha256_digest': sha256_digest, + }) + files = [('content', os.path.basename(filename), file_data)] + if sig_file: + with open(sig_file, 'rb') as f: + sig_data = f.read() + files.append(('gpg_signature', os.path.basename(sig_file), + sig_data)) + shutil.rmtree(os.path.dirname(sig_file)) + request = self.encode_request(d.items(), files) + return self.send_request(request) + + def upload_documentation(self, metadata, doc_dir): + """ + Upload documentation to the index. + + :param metadata: A :class:`Metadata` instance defining at least a name + and version number for the documentation to be + uploaded. + :param doc_dir: The pathname of the directory which contains the + documentation. This should be the directory that + contains the ``index.html`` for the documentation. + :return: The HTTP response received from PyPI upon submission of the + request. + """ + self.check_credentials() + if not os.path.isdir(doc_dir): + raise DistlibException('not a directory: %r' % doc_dir) + fn = os.path.join(doc_dir, 'index.html') + if not os.path.exists(fn): + raise DistlibException('not found: %r' % fn) + metadata.validate() + name, version = metadata.name, metadata.version + zip_data = zip_dir(doc_dir).getvalue() + fields = [(':action', 'doc_upload'), + ('name', name), ('version', version)] + files = [('content', name, zip_data)] + request = self.encode_request(fields, files) + return self.send_request(request) + + def get_verify_command(self, signature_filename, data_filename, + keystore=None): + """ + Return a suitable command for verifying a file. + + :param signature_filename: The pathname to the file containing the + signature. + :param data_filename: The pathname to the file containing the + signed data. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: The verifying command as a list suitable to be + passed to :class:`subprocess.Popen`. + """ + cmd = [self.gpg, '--status-fd', '2', '--no-tty'] + if keystore is None: + keystore = self.gpg_home + if keystore: + cmd.extend(['--homedir', keystore]) + cmd.extend(['--verify', signature_filename, data_filename]) + logger.debug('invoking: %s', ' '.join(cmd)) + return cmd + + def verify_signature(self, signature_filename, data_filename, + keystore=None): + """ + Verify a signature for a file. + + :param signature_filename: The pathname to the file containing the + signature. + :param data_filename: The pathname to the file containing the + signed data. + :param keystore: The path to a directory which contains the keys + used in verification. If not specified, the + instance's ``gpg_home`` attribute is used instead. + :return: True if the signature was verified, else False. + """ + if not self.gpg: + raise DistlibException('verification unavailable because gpg ' + 'unavailable') + cmd = self.get_verify_command(signature_filename, data_filename, + keystore) + rc, stdout, stderr = self.run_command(cmd) + if rc not in (0, 1): + raise DistlibException('verify command failed with error ' + 'code %s' % rc) + return rc == 0 + + def download_file(self, url, destfile, digest=None, reporthook=None): + """ + This is a convenience method for downloading a file from an URL. + Normally, this will be a file from the index, though currently + no check is made for this (i.e. a file can be downloaded from + anywhere). + + The method is just like the :func:`urlretrieve` function in the + standard library, except that it allows digest computation to be + done during download and checking that the downloaded data + matched any expected value. + + :param url: The URL of the file to be downloaded (assumed to be + available via an HTTP GET request). + :param destfile: The pathname where the downloaded file is to be + saved. + :param digest: If specified, this must be a (hasher, value) + tuple, where hasher is the algorithm used (e.g. + ``'md5'``) and ``value`` is the expected value. + :param reporthook: The same as for :func:`urlretrieve` in the + standard library. + """ + if digest is None: + digester = None + logger.debug('No digest specified') + else: + if isinstance(digest, (list, tuple)): + hasher, digest = digest + else: + hasher = 'md5' + digester = getattr(hashlib, hasher)() + logger.debug('Digest specified: %s' % digest) + # The following code is equivalent to urlretrieve. + # We need to do it this way so that we can compute the + # digest of the file as we go. + with open(destfile, 'wb') as dfp: + # addinfourl is not a context manager on 2.x + # so we have to use try/finally + sfp = self.send_request(Request(url)) + try: + headers = sfp.info() + blocksize = 8192 + size = -1 + read = 0 + blocknum = 0 + if "content-length" in headers: + size = int(headers["Content-Length"]) + if reporthook: + reporthook(blocknum, blocksize, size) + while True: + block = sfp.read(blocksize) + if not block: + break + read += len(block) + dfp.write(block) + if digester: + digester.update(block) + blocknum += 1 + if reporthook: + reporthook(blocknum, blocksize, size) + finally: + sfp.close() + + # check that we got the whole file, if we can + if size >= 0 and read < size: + raise DistlibException( + 'retrieval incomplete: got only %d out of %d bytes' + % (read, size)) + # if we have a digest, it must match. + if digester: + actual = digester.hexdigest() + if digest != actual: + raise DistlibException('%s digest mismatch for %s: expected ' + '%s, got %s' % (hasher, destfile, + digest, actual)) + logger.debug('Digest verified: %s', digest) + + def send_request(self, req): + """ + Send a standard library :class:`Request` to PyPI and return its + response. + + :param req: The request to send. + :return: The HTTP response from PyPI (a standard library HTTPResponse). + """ + handlers = [] + if self.password_handler: + handlers.append(self.password_handler) + if self.ssl_verifier: + handlers.append(self.ssl_verifier) + opener = build_opener(*handlers) + return opener.open(req) + + def encode_request(self, fields, files): + """ + Encode fields and files for posting to an HTTP server. + + :param fields: The fields to send as a list of (fieldname, value) + tuples. + :param files: The files to send as a list of (fieldname, filename, + file_bytes) tuple. + """ + # Adapted from packaging, which in turn was adapted from + # http://code.activestate.com/recipes/146306 + + parts = [] + boundary = self.boundary + for k, values in fields: + if not isinstance(values, (list, tuple)): + values = [values] + + for v in values: + parts.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"' % + k).encode('utf-8'), + b'', + v.encode('utf-8'))) + for key, filename, value in files: + parts.extend(( + b'--' + boundary, + ('Content-Disposition: form-data; name="%s"; filename="%s"' % + (key, filename)).encode('utf-8'), + b'', + value)) + + parts.extend((b'--' + boundary + b'--', b'')) + + body = b'\r\n'.join(parts) + ct = b'multipart/form-data; boundary=' + boundary + headers = { + 'Content-type': ct, + 'Content-length': str(len(body)) + } + return Request(self.url, body, headers) + + def search(self, terms, operator=None): + if isinstance(terms, string_types): + terms = {'name': terms} + rpc_proxy = ServerProxy(self.url, timeout=3.0) + try: + return rpc_proxy.search(terms, operator or 'and') + finally: + rpc_proxy('close')() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/locators.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/locators.py new file mode 100644 index 0000000..5c655c3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/locators.py @@ -0,0 +1,1295 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2015 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# + +import gzip +from io import BytesIO +import json +import logging +import os +import posixpath +import re +try: + import threading +except ImportError: # pragma: no cover + import dummy_threading as threading +import zlib + +from . import DistlibException +from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, + queue, quote, unescape, string_types, build_opener, + HTTPRedirectHandler as BaseRedirectHandler, text_type, + Request, HTTPError, URLError) +from .database import Distribution, DistributionPath, make_dist +from .metadata import Metadata, MetadataInvalidError +from .util import (cached_property, parse_credentials, ensure_slash, + split_filename, get_project_data, parse_requirement, + parse_name_and_version, ServerProxy, normalize_name) +from .version import get_scheme, UnsupportedVersionError +from .wheel import Wheel, is_compatible + +logger = logging.getLogger(__name__) + +HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)') +CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I) +HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml') +DEFAULT_INDEX = 'https://pypi.python.org/pypi' + +def get_all_distribution_names(url=None): + """ + Return all distribution names known by an index. + :param url: The URL of the index. + :return: A list of all known distribution names. + """ + if url is None: + url = DEFAULT_INDEX + client = ServerProxy(url, timeout=3.0) + try: + return client.list_packages() + finally: + client('close')() + +class RedirectHandler(BaseRedirectHandler): + """ + A class to work around a bug in some Python 3.2.x releases. + """ + # There's a bug in the base version for some 3.2.x + # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header + # returns e.g. /abc, it bails because it says the scheme '' + # is bogus, when actually it should use the request's + # URL for the scheme. See Python issue #13696. + def http_error_302(self, req, fp, code, msg, headers): + # Some servers (incorrectly) return multiple Location headers + # (so probably same goes for URI). Use first header. + newurl = None + for key in ('location', 'uri'): + if key in headers: + newurl = headers[key] + break + if newurl is None: # pragma: no cover + return + urlparts = urlparse(newurl) + if urlparts.scheme == '': + newurl = urljoin(req.get_full_url(), newurl) + if hasattr(headers, 'replace_header'): + headers.replace_header(key, newurl) + else: + headers[key] = newurl + return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, + headers) + + http_error_301 = http_error_303 = http_error_307 = http_error_302 + +class Locator(object): + """ + A base class for locators - things that locate distributions. + """ + source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz') + binary_extensions = ('.egg', '.exe', '.whl') + excluded_extensions = ('.pdf',) + + # A list of tags indicating which wheels you want to match. The default + # value of None matches against the tags compatible with the running + # Python. If you want to match other values, set wheel_tags on a locator + # instance to a list of tuples (pyver, abi, arch) which you want to match. + wheel_tags = None + + downloadable_extensions = source_extensions + ('.whl',) + + def __init__(self, scheme='default'): + """ + Initialise an instance. + :param scheme: Because locators look for most recent versions, they + need to know the version scheme to use. This specifies + the current PEP-recommended scheme - use ``'legacy'`` + if you need to support existing distributions on PyPI. + """ + self._cache = {} + self.scheme = scheme + # Because of bugs in some of the handlers on some of the platforms, + # we use our own opener rather than just using urlopen. + self.opener = build_opener(RedirectHandler()) + # If get_project() is called from locate(), the matcher instance + # is set from the requirement passed to locate(). See issue #18 for + # why this can be useful to know. + self.matcher = None + self.errors = queue.Queue() + + def get_errors(self): + """ + Return any errors which have occurred. + """ + result = [] + while not self.errors.empty(): # pragma: no cover + try: + e = self.errors.get(False) + result.append(e) + except self.errors.Empty: + continue + self.errors.task_done() + return result + + def clear_errors(self): + """ + Clear any errors which may have been logged. + """ + # Just get the errors and throw them away + self.get_errors() + + def clear_cache(self): + self._cache.clear() + + def _get_scheme(self): + return self._scheme + + def _set_scheme(self, value): + self._scheme = value + + scheme = property(_get_scheme, _set_scheme) + + def _get_project(self, name): + """ + For a given project, get a dictionary mapping available versions to Distribution + instances. + + This should be implemented in subclasses. + + If called from a locate() request, self.matcher will be set to a + matcher for the requirement to satisfy, otherwise it will be None. + """ + raise NotImplementedError('Please implement in the subclass') + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Please implement in the subclass') + + def get_project(self, name): + """ + For a given project, get a dictionary mapping available versions to Distribution + instances. + + This calls _get_project to do all the work, and just implements a caching layer on top. + """ + if self._cache is None: # pragma: no cover + result = self._get_project(name) + elif name in self._cache: + result = self._cache[name] + else: + self.clear_errors() + result = self._get_project(name) + self._cache[name] = result + return result + + def score_url(self, url): + """ + Give an url a score which can be used to choose preferred URLs + for a given project release. + """ + t = urlparse(url) + basename = posixpath.basename(t.path) + compatible = True + is_wheel = basename.endswith('.whl') + is_downloadable = basename.endswith(self.downloadable_extensions) + if is_wheel: + compatible = is_compatible(Wheel(basename), self.wheel_tags) + return (t.scheme == 'https', 'pypi.python.org' in t.netloc, + is_downloadable, is_wheel, compatible, basename) + + def prefer_url(self, url1, url2): + """ + Choose one of two URLs where both are candidates for distribution + archives for the same version of a distribution (for example, + .tar.gz vs. zip). + + The current implementation favours https:// URLs over http://, archives + from PyPI over those from other locations, wheel compatibility (if a + wheel) and then the archive name. + """ + result = url2 + if url1: + s1 = self.score_url(url1) + s2 = self.score_url(url2) + if s1 > s2: + result = url1 + if result != url2: + logger.debug('Not replacing %r with %r', url1, url2) + else: + logger.debug('Replacing %r with %r', url1, url2) + return result + + def split_filename(self, filename, project_name): + """ + Attempt to split a filename in project name, version and Python version. + """ + return split_filename(filename, project_name) + + def convert_url_to_download_info(self, url, project_name): + """ + See if a URL is a candidate for a download URL for a project (the URL + has typically been scraped from an HTML page). + + If it is, a dictionary is returned with keys "name", "version", + "filename" and "url"; otherwise, None is returned. + """ + def same_project(name1, name2): + return normalize_name(name1) == normalize_name(name2) + + result = None + scheme, netloc, path, params, query, frag = urlparse(url) + if frag.lower().startswith('egg='): # pragma: no cover + logger.debug('%s: version hint in fragment: %r', + project_name, frag) + m = HASHER_HASH.match(frag) + if m: + algo, digest = m.groups() + else: + algo, digest = None, None + origpath = path + if path and path[-1] == '/': # pragma: no cover + path = path[:-1] + if path.endswith('.whl'): + try: + wheel = Wheel(path) + if not is_compatible(wheel, self.wheel_tags): + logger.debug('Wheel not compatible: %s', path) + else: + if project_name is None: + include = True + else: + include = same_project(wheel.name, project_name) + if include: + result = { + 'name': wheel.name, + 'version': wheel.version, + 'filename': wheel.filename, + 'url': urlunparse((scheme, netloc, origpath, + params, query, '')), + 'python-version': ', '.join( + ['.'.join(list(v[2:])) for v in wheel.pyver]), + } + except Exception as e: # pragma: no cover + logger.warning('invalid path for wheel: %s', path) + elif not path.endswith(self.downloadable_extensions): # pragma: no cover + logger.debug('Not downloadable: %s', path) + else: # downloadable extension + path = filename = posixpath.basename(path) + for ext in self.downloadable_extensions: + if path.endswith(ext): + path = path[:-len(ext)] + t = self.split_filename(path, project_name) + if not t: # pragma: no cover + logger.debug('No match for project/version: %s', path) + else: + name, version, pyver = t + if not project_name or same_project(project_name, name): + result = { + 'name': name, + 'version': version, + 'filename': filename, + 'url': urlunparse((scheme, netloc, origpath, + params, query, '')), + #'packagetype': 'sdist', + } + if pyver: # pragma: no cover + result['python-version'] = pyver + break + if result and algo: + result['%s_digest' % algo] = digest + return result + + def _get_digest(self, info): + """ + Get a digest from a dictionary by looking at keys of the form + 'algo_digest'. + + Returns a 2-tuple (algo, digest) if found, else None. Currently + looks only for SHA256, then MD5. + """ + result = None + for algo in ('sha256', 'md5'): + key = '%s_digest' % algo + if key in info: + result = (algo, info[key]) + break + return result + + def _update_version_data(self, result, info): + """ + Update a result dictionary (the final result from _get_project) with a + dictionary for a specific version, which typically holds information + gleaned from a filename or URL for an archive for the distribution. + """ + name = info.pop('name') + version = info.pop('version') + if version in result: + dist = result[version] + md = dist.metadata + else: + dist = make_dist(name, version, scheme=self.scheme) + md = dist.metadata + dist.digest = digest = self._get_digest(info) + url = info['url'] + result['digests'][url] = digest + if md.source_url != info['url']: + md.source_url = self.prefer_url(md.source_url, url) + result['urls'].setdefault(version, set()).add(url) + dist.locator = self + result[version] = dist + + def locate(self, requirement, prereleases=False): + """ + Find the most recent distribution which matches the given + requirement. + + :param requirement: A requirement of the form 'foo (1.0)' or perhaps + 'foo (>= 1.0, < 2.0, != 1.3)' + :param prereleases: If ``True``, allow pre-release versions + to be located. Otherwise, pre-release versions + are not returned. + :return: A :class:`Distribution` instance, or ``None`` if no such + distribution could be located. + """ + result = None + r = parse_requirement(requirement) + if r is None: # pragma: no cover + raise DistlibException('Not a valid requirement: %r' % requirement) + scheme = get_scheme(self.scheme) + self.matcher = matcher = scheme.matcher(r.requirement) + logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__) + versions = self.get_project(r.name) + if len(versions) > 2: # urls and digests keys are present + # sometimes, versions are invalid + slist = [] + vcls = matcher.version_class + for k in versions: + if k in ('urls', 'digests'): + continue + try: + if not matcher.match(k): + logger.debug('%s did not match %r', matcher, k) + else: + if prereleases or not vcls(k).is_prerelease: + slist.append(k) + else: + logger.debug('skipping pre-release ' + 'version %s of %s', k, matcher.name) + except Exception: # pragma: no cover + logger.warning('error matching %s with %r', matcher, k) + pass # slist.append(k) + if len(slist) > 1: + slist = sorted(slist, key=scheme.key) + if slist: + logger.debug('sorted list: %s', slist) + version = slist[-1] + result = versions[version] + if result: + if r.extras: + result.extras = r.extras + result.download_urls = versions.get('urls', {}).get(version, set()) + d = {} + sd = versions.get('digests', {}) + for url in result.download_urls: + if url in sd: # pragma: no cover + d[url] = sd[url] + result.digests = d + self.matcher = None + return result + + +class PyPIRPCLocator(Locator): + """ + This locator uses XML-RPC to locate distributions. It therefore + cannot be used with simple mirrors (that only mirror file content). + """ + def __init__(self, url, **kwargs): + """ + Initialise an instance. + + :param url: The URL to use for XML-RPC. + :param kwargs: Passed to the superclass constructor. + """ + super(PyPIRPCLocator, self).__init__(**kwargs) + self.base_url = url + self.client = ServerProxy(url, timeout=3.0) + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + return set(self.client.list_packages()) + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + versions = self.client.package_releases(name, True) + for v in versions: + urls = self.client.release_urls(name, v) + data = self.client.release_data(name, v) + metadata = Metadata(scheme=self.scheme) + metadata.name = data['name'] + metadata.version = data['version'] + metadata.license = data.get('license') + metadata.keywords = data.get('keywords', []) + metadata.summary = data.get('summary') + dist = Distribution(metadata) + if urls: + info = urls[0] + metadata.source_url = info['url'] + dist.digest = self._get_digest(info) + dist.locator = self + result[v] = dist + for info in urls: + url = info['url'] + digest = self._get_digest(info) + result['urls'].setdefault(v, set()).add(url) + result['digests'][url] = digest + return result + +class PyPIJSONLocator(Locator): + """ + This locator uses PyPI's JSON interface. It's very limited in functionality + and probably not worth using. + """ + def __init__(self, url, **kwargs): + super(PyPIJSONLocator, self).__init__(**kwargs) + self.base_url = ensure_slash(url) + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Not available from this locator') + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + url = urljoin(self.base_url, '%s/json' % quote(name)) + try: + resp = self.opener.open(url) + data = resp.read().decode() # for now + d = json.loads(data) + md = Metadata(scheme=self.scheme) + data = d['info'] + md.name = data['name'] + md.version = data['version'] + md.license = data.get('license') + md.keywords = data.get('keywords', []) + md.summary = data.get('summary') + dist = Distribution(md) + dist.locator = self + urls = d['urls'] + result[md.version] = dist + for info in d['urls']: + url = info['url'] + dist.download_urls.add(url) + dist.digests[url] = self._get_digest(info) + result['urls'].setdefault(md.version, set()).add(url) + result['digests'][url] = self._get_digest(info) + # Now get other releases + for version, infos in d['releases'].items(): + if version == md.version: + continue # already done + omd = Metadata(scheme=self.scheme) + omd.name = md.name + omd.version = version + odist = Distribution(omd) + odist.locator = self + result[version] = odist + for info in infos: + url = info['url'] + odist.download_urls.add(url) + odist.digests[url] = self._get_digest(info) + result['urls'].setdefault(version, set()).add(url) + result['digests'][url] = self._get_digest(info) +# for info in urls: +# md.source_url = info['url'] +# dist.digest = self._get_digest(info) +# dist.locator = self +# for info in urls: +# url = info['url'] +# result['urls'].setdefault(md.version, set()).add(url) +# result['digests'][url] = self._get_digest(info) + except Exception as e: + self.errors.put(text_type(e)) + logger.exception('JSON fetch failed: %s', e) + return result + + +class Page(object): + """ + This class represents a scraped HTML page. + """ + # The following slightly hairy-looking regex just looks for the contents of + # an anchor link, which has an attribute "href" either immediately preceded + # or immediately followed by a "rel" attribute. The attribute values can be + # declared with double quotes, single quotes or no quotes - which leads to + # the length of the expression. + _href = re.compile(""" +(rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)? +href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*)) +(\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))? +""", re.I | re.S | re.X) + _base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I | re.S) + + def __init__(self, data, url): + """ + Initialise an instance with the Unicode page contents and the URL they + came from. + """ + self.data = data + self.base_url = self.url = url + m = self._base.search(self.data) + if m: + self.base_url = m.group(1) + + _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I) + + @cached_property + def links(self): + """ + Return the URLs of all the links on a page together with information + about their "rel" attribute, for determining which ones to treat as + downloads and which ones to queue for further scraping. + """ + def clean(url): + "Tidy up an URL." + scheme, netloc, path, params, query, frag = urlparse(url) + return urlunparse((scheme, netloc, quote(path), + params, query, frag)) + + result = set() + for match in self._href.finditer(self.data): + d = match.groupdict('') + rel = (d['rel1'] or d['rel2'] or d['rel3'] or + d['rel4'] or d['rel5'] or d['rel6']) + url = d['url1'] or d['url2'] or d['url3'] + url = urljoin(self.base_url, url) + url = unescape(url) + url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url) + result.add((url, rel)) + # We sort the result, hoping to bring the most recent versions + # to the front + result = sorted(result, key=lambda t: t[0], reverse=True) + return result + + +class SimpleScrapingLocator(Locator): + """ + A locator which scrapes HTML pages to locate downloads for a distribution. + This runs multiple threads to do the I/O; performance is at least as good + as pip's PackageFinder, which works in an analogous fashion. + """ + + # These are used to deal with various Content-Encoding schemes. + decoders = { + 'deflate': zlib.decompress, + 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(d)).read(), + 'none': lambda b: b, + } + + def __init__(self, url, timeout=None, num_workers=10, **kwargs): + """ + Initialise an instance. + :param url: The root URL to use for scraping. + :param timeout: The timeout, in seconds, to be applied to requests. + This defaults to ``None`` (no timeout specified). + :param num_workers: The number of worker threads you want to do I/O, + This defaults to 10. + :param kwargs: Passed to the superclass. + """ + super(SimpleScrapingLocator, self).__init__(**kwargs) + self.base_url = ensure_slash(url) + self.timeout = timeout + self._page_cache = {} + self._seen = set() + self._to_fetch = queue.Queue() + self._bad_hosts = set() + self.skip_externals = False + self.num_workers = num_workers + self._lock = threading.RLock() + # See issue #45: we need to be resilient when the locator is used + # in a thread, e.g. with concurrent.futures. We can't use self._lock + # as it is for coordinating our internal threads - the ones created + # in _prepare_threads. + self._gplock = threading.RLock() + self.platform_check = False # See issue #112 + + def _prepare_threads(self): + """ + Threads are created only when get_project is called, and terminate + before it returns. They are there primarily to parallelise I/O (i.e. + fetching web pages). + """ + self._threads = [] + for i in range(self.num_workers): + t = threading.Thread(target=self._fetch) + t.setDaemon(True) + t.start() + self._threads.append(t) + + def _wait_threads(self): + """ + Tell all the threads to terminate (by sending a sentinel value) and + wait for them to do so. + """ + # Note that you need two loops, since you can't say which + # thread will get each sentinel + for t in self._threads: + self._to_fetch.put(None) # sentinel + for t in self._threads: + t.join() + self._threads = [] + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + with self._gplock: + self.result = result + self.project_name = name + url = urljoin(self.base_url, '%s/' % quote(name)) + self._seen.clear() + self._page_cache.clear() + self._prepare_threads() + try: + logger.debug('Queueing %s', url) + self._to_fetch.put(url) + self._to_fetch.join() + finally: + self._wait_threads() + del self.result + return result + + platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|' + r'win(32|_amd64)|macosx_?\d+)\b', re.I) + + def _is_platform_dependent(self, url): + """ + Does an URL refer to a platform-specific download? + """ + return self.platform_dependent.search(url) + + def _process_download(self, url): + """ + See if an URL is a suitable download for a project. + + If it is, register information in the result dictionary (for + _get_project) about the specific version it's for. + + Note that the return value isn't actually used other than as a boolean + value. + """ + if self.platform_check and self._is_platform_dependent(url): + info = None + else: + info = self.convert_url_to_download_info(url, self.project_name) + logger.debug('process_download: %s -> %s', url, info) + if info: + with self._lock: # needed because self.result is shared + self._update_version_data(self.result, info) + return info + + def _should_queue(self, link, referrer, rel): + """ + Determine whether a link URL from a referring page and with a + particular "rel" attribute should be queued for scraping. + """ + scheme, netloc, path, _, _, _ = urlparse(link) + if path.endswith(self.source_extensions + self.binary_extensions + + self.excluded_extensions): + result = False + elif self.skip_externals and not link.startswith(self.base_url): + result = False + elif not referrer.startswith(self.base_url): + result = False + elif rel not in ('homepage', 'download'): + result = False + elif scheme not in ('http', 'https', 'ftp'): + result = False + elif self._is_platform_dependent(link): + result = False + else: + host = netloc.split(':', 1)[0] + if host.lower() == 'localhost': + result = False + else: + result = True + logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, + referrer, result) + return result + + def _fetch(self): + """ + Get a URL to fetch from the work queue, get the HTML page, examine its + links for download candidates and candidates for further scraping. + + This is a handy method to run in a thread. + """ + while True: + url = self._to_fetch.get() + try: + if url: + page = self.get_page(url) + if page is None: # e.g. after an error + continue + for link, rel in page.links: + if link not in self._seen: + try: + self._seen.add(link) + if (not self._process_download(link) and + self._should_queue(link, url, rel)): + logger.debug('Queueing %s from %s', link, url) + self._to_fetch.put(link) + except MetadataInvalidError: # e.g. invalid versions + pass + except Exception as e: # pragma: no cover + self.errors.put(text_type(e)) + finally: + # always do this, to avoid hangs :-) + self._to_fetch.task_done() + if not url: + #logger.debug('Sentinel seen, quitting.') + break + + def get_page(self, url): + """ + Get the HTML for an URL, possibly from an in-memory cache. + + XXX TODO Note: this cache is never actually cleared. It's assumed that + the data won't get stale over the lifetime of a locator instance (not + necessarily true for the default_locator). + """ + # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api + scheme, netloc, path, _, _, _ = urlparse(url) + if scheme == 'file' and os.path.isdir(url2pathname(path)): + url = urljoin(ensure_slash(url), 'index.html') + + if url in self._page_cache: + result = self._page_cache[url] + logger.debug('Returning %s from cache: %s', url, result) + else: + host = netloc.split(':', 1)[0] + result = None + if host in self._bad_hosts: + logger.debug('Skipping %s due to bad host %s', url, host) + else: + req = Request(url, headers={'Accept-encoding': 'identity'}) + try: + logger.debug('Fetching %s', url) + resp = self.opener.open(req, timeout=self.timeout) + logger.debug('Fetched %s', url) + headers = resp.info() + content_type = headers.get('Content-Type', '') + if HTML_CONTENT_TYPE.match(content_type): + final_url = resp.geturl() + data = resp.read() + encoding = headers.get('Content-Encoding') + if encoding: + decoder = self.decoders[encoding] # fail if not found + data = decoder(data) + encoding = 'utf-8' + m = CHARSET.search(content_type) + if m: + encoding = m.group(1) + try: + data = data.decode(encoding) + except UnicodeError: # pragma: no cover + data = data.decode('latin-1') # fallback + result = Page(data, final_url) + self._page_cache[final_url] = result + except HTTPError as e: + if e.code != 404: + logger.exception('Fetch failed: %s: %s', url, e) + except URLError as e: # pragma: no cover + logger.exception('Fetch failed: %s: %s', url, e) + with self._lock: + self._bad_hosts.add(host) + except Exception as e: # pragma: no cover + logger.exception('Fetch failed: %s: %s', url, e) + finally: + self._page_cache[url] = result # even if None (failure) + return result + + _distname_re = re.compile('<a href=[^>]*>([^<]+)<') + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + page = self.get_page(self.base_url) + if not page: + raise DistlibException('Unable to get %s' % self.base_url) + for match in self._distname_re.finditer(page.data): + result.add(match.group(1)) + return result + +class DirectoryLocator(Locator): + """ + This class locates distributions in a directory tree. + """ + + def __init__(self, path, **kwargs): + """ + Initialise an instance. + :param path: The root of the directory tree to search. + :param kwargs: Passed to the superclass constructor, + except for: + * recursive - if True (the default), subdirectories are + recursed into. If False, only the top-level directory + is searched, + """ + self.recursive = kwargs.pop('recursive', True) + super(DirectoryLocator, self).__init__(**kwargs) + path = os.path.abspath(path) + if not os.path.isdir(path): # pragma: no cover + raise DistlibException('Not a directory: %r' % path) + self.base_dir = path + + def should_include(self, filename, parent): + """ + Should a filename be considered as a candidate for a distribution + archive? As well as the filename, the directory which contains it + is provided, though not used by the current implementation. + """ + return filename.endswith(self.downloadable_extensions) + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + for root, dirs, files in os.walk(self.base_dir): + for fn in files: + if self.should_include(fn, root): + fn = os.path.join(root, fn) + url = urlunparse(('file', '', + pathname2url(os.path.abspath(fn)), + '', '', '')) + info = self.convert_url_to_download_info(url, name) + if info: + self._update_version_data(result, info) + if not self.recursive: + break + return result + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + for root, dirs, files in os.walk(self.base_dir): + for fn in files: + if self.should_include(fn, root): + fn = os.path.join(root, fn) + url = urlunparse(('file', '', + pathname2url(os.path.abspath(fn)), + '', '', '')) + info = self.convert_url_to_download_info(url, None) + if info: + result.add(info['name']) + if not self.recursive: + break + return result + +class JSONLocator(Locator): + """ + This locator uses special extended metadata (not available on PyPI) and is + the basis of performant dependency resolution in distlib. Other locators + require archive downloads before dependencies can be determined! As you + might imagine, that can be slow. + """ + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + raise NotImplementedError('Not available from this locator') + + def _get_project(self, name): + result = {'urls': {}, 'digests': {}} + data = get_project_data(name) + if data: + for info in data.get('files', []): + if info['ptype'] != 'sdist' or info['pyversion'] != 'source': + continue + # We don't store summary in project metadata as it makes + # the data bigger for no benefit during dependency + # resolution + dist = make_dist(data['name'], info['version'], + summary=data.get('summary', + 'Placeholder for summary'), + scheme=self.scheme) + md = dist.metadata + md.source_url = info['url'] + # TODO SHA256 digest + if 'digest' in info and info['digest']: + dist.digest = ('md5', info['digest']) + md.dependencies = info.get('requirements', {}) + dist.exports = info.get('exports', {}) + result[dist.version] = dist + result['urls'].setdefault(dist.version, set()).add(info['url']) + return result + +class DistPathLocator(Locator): + """ + This locator finds installed distributions in a path. It can be useful for + adding to an :class:`AggregatingLocator`. + """ + def __init__(self, distpath, **kwargs): + """ + Initialise an instance. + + :param distpath: A :class:`DistributionPath` instance to search. + """ + super(DistPathLocator, self).__init__(**kwargs) + assert isinstance(distpath, DistributionPath) + self.distpath = distpath + + def _get_project(self, name): + dist = self.distpath.get_distribution(name) + if dist is None: + result = {'urls': {}, 'digests': {}} + else: + result = { + dist.version: dist, + 'urls': {dist.version: set([dist.source_url])}, + 'digests': {dist.version: set([None])} + } + return result + + +class AggregatingLocator(Locator): + """ + This class allows you to chain and/or merge a list of locators. + """ + def __init__(self, *locators, **kwargs): + """ + Initialise an instance. + + :param locators: The list of locators to search. + :param kwargs: Passed to the superclass constructor, + except for: + * merge - if False (the default), the first successful + search from any of the locators is returned. If True, + the results from all locators are merged (this can be + slow). + """ + self.merge = kwargs.pop('merge', False) + self.locators = locators + super(AggregatingLocator, self).__init__(**kwargs) + + def clear_cache(self): + super(AggregatingLocator, self).clear_cache() + for locator in self.locators: + locator.clear_cache() + + def _set_scheme(self, value): + self._scheme = value + for locator in self.locators: + locator.scheme = value + + scheme = property(Locator.scheme.fget, _set_scheme) + + def _get_project(self, name): + result = {} + for locator in self.locators: + d = locator.get_project(name) + if d: + if self.merge: + files = result.get('urls', {}) + digests = result.get('digests', {}) + # next line could overwrite result['urls'], result['digests'] + result.update(d) + df = result.get('urls') + if files and df: + for k, v in files.items(): + if k in df: + df[k] |= v + else: + df[k] = v + dd = result.get('digests') + if digests and dd: + dd.update(digests) + else: + # See issue #18. If any dists are found and we're looking + # for specific constraints, we only return something if + # a match is found. For example, if a DirectoryLocator + # returns just foo (1.0) while we're looking for + # foo (>= 2.0), we'll pretend there was nothing there so + # that subsequent locators can be queried. Otherwise we + # would just return foo (1.0) which would then lead to a + # failure to find foo (>= 2.0), because other locators + # weren't searched. Note that this only matters when + # merge=False. + if self.matcher is None: + found = True + else: + found = False + for k in d: + if self.matcher.match(k): + found = True + break + if found: + result = d + break + return result + + def get_distribution_names(self): + """ + Return all the distribution names known to this locator. + """ + result = set() + for locator in self.locators: + try: + result |= locator.get_distribution_names() + except NotImplementedError: + pass + return result + + +# 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. +default_locator = AggregatingLocator( + JSONLocator(), + SimpleScrapingLocator('https://pypi.python.org/simple/', + timeout=3.0), + scheme='legacy') + +locate = default_locator.locate + +NAME_VERSION_RE = re.compile(r'(?P<name>[\w-]+)\s*' + r'\(\s*(==\s*)?(?P<ver>[^)]+)\)$') + +class DependencyFinder(object): + """ + Locate dependencies for distributions. + """ + + def __init__(self, locator=None): + """ + Initialise an instance, using the specified locator + to locate distributions. + """ + self.locator = locator or default_locator + self.scheme = get_scheme(self.locator.scheme) + + def add_distribution(self, dist): + """ + Add a distribution to the finder. This will update internal information + about who provides what. + :param dist: The distribution to add. + """ + logger.debug('adding distribution %s', dist) + name = dist.key + self.dists_by_name[name] = dist + self.dists[(name, dist.version)] = dist + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Add to provided: %s, %s, %s', name, version, dist) + self.provided.setdefault(name, set()).add((version, dist)) + + def remove_distribution(self, dist): + """ + Remove a distribution from the finder. This will update internal + information about who provides what. + :param dist: The distribution to remove. + """ + logger.debug('removing distribution %s', dist) + name = dist.key + del self.dists_by_name[name] + del self.dists[(name, dist.version)] + for p in dist.provides: + name, version = parse_name_and_version(p) + logger.debug('Remove from provided: %s, %s, %s', name, version, dist) + s = self.provided[name] + s.remove((version, dist)) + if not s: + del self.provided[name] + + def get_matcher(self, reqt): + """ + Get a version matcher for a requirement. + :param reqt: The requirement + :type reqt: str + :return: A version matcher (an instance of + :class:`distlib.version.Matcher`). + """ + try: + matcher = self.scheme.matcher(reqt) + except UnsupportedVersionError: # pragma: no cover + # XXX compat-mode if cannot read the version + name = reqt.split()[0] + matcher = self.scheme.matcher(name) + return matcher + + def find_providers(self, reqt): + """ + Find the distributions which can fulfill a requirement. + + :param reqt: The requirement. + :type reqt: str + :return: A set of distribution which can fulfill the requirement. + """ + matcher = self.get_matcher(reqt) + name = matcher.key # case-insensitive + result = set() + provided = self.provided + if name in provided: + for version, provider in provided[name]: + try: + match = matcher.match(version) + except UnsupportedVersionError: + match = False + + if match: + result.add(provider) + break + return result + + def try_to_replace(self, provider, other, problems): + """ + Attempt to replace one provider with another. This is typically used + when resolving dependencies from multiple sources, e.g. A requires + (B >= 1.0) while C requires (B >= 1.1). + + For successful replacement, ``provider`` must meet all the requirements + which ``other`` fulfills. + + :param provider: The provider we are trying to replace with. + :param other: The provider we're trying to replace. + :param problems: If False is returned, this will contain what + problems prevented replacement. This is currently + a tuple of the literal string 'cantreplace', + ``provider``, ``other`` and the set of requirements + that ``provider`` couldn't fulfill. + :return: True if we can replace ``other`` with ``provider``, else + False. + """ + rlist = self.reqts[other] + unmatched = set() + for s in rlist: + matcher = self.get_matcher(s) + if not matcher.match(provider.version): + unmatched.add(s) + if unmatched: + # can't replace other with provider + problems.add(('cantreplace', provider, other, + frozenset(unmatched))) + result = False + else: + # can replace other with provider + self.remove_distribution(other) + del self.reqts[other] + for s in rlist: + self.reqts.setdefault(provider, set()).add(s) + self.add_distribution(provider) + result = True + return result + + def find(self, requirement, meta_extras=None, prereleases=False): + """ + Find a distribution and all distributions it depends on. + + :param requirement: The requirement specifying the distribution to + find, or a Distribution instance. + :param meta_extras: A list of meta extras such as :test:, :build: and + so on. + :param prereleases: If ``True``, allow pre-release versions to be + returned - otherwise, don't return prereleases + unless they're all that's available. + + Return a set of :class:`Distribution` instances and a set of + problems. + + The distributions returned should be such that they have the + :attr:`required` attribute set to ``True`` if they were + from the ``requirement`` passed to ``find()``, and they have the + :attr:`build_time_dependency` attribute set to ``True`` unless they + are post-installation dependencies of the ``requirement``. + + The problems should be a tuple consisting of the string + ``'unsatisfied'`` and the requirement which couldn't be satisfied + by any distribution known to the locator. + """ + + self.provided = {} + self.dists = {} + self.dists_by_name = {} + self.reqts = {} + + meta_extras = set(meta_extras or []) + if ':*:' in meta_extras: + meta_extras.remove(':*:') + # :meta: and :run: are implicitly included + meta_extras |= set([':test:', ':build:', ':dev:']) + + if isinstance(requirement, Distribution): + dist = odist = requirement + logger.debug('passed %s as requirement', odist) + else: + dist = odist = self.locator.locate(requirement, + prereleases=prereleases) + if dist is None: + raise DistlibException('Unable to locate %r' % requirement) + logger.debug('located %s', odist) + dist.requested = True + problems = set() + todo = set([dist]) + install_dists = set([odist]) + while todo: + dist = todo.pop() + name = dist.key # case-insensitive + if name not in self.dists_by_name: + self.add_distribution(dist) + else: + #import pdb; pdb.set_trace() + other = self.dists_by_name[name] + if other != dist: + self.try_to_replace(dist, other, problems) + + ireqts = dist.run_requires | dist.meta_requires + sreqts = dist.build_requires + ereqts = set() + if meta_extras and dist in install_dists: + for key in ('test', 'build', 'dev'): + e = ':%s:' % key + if e in meta_extras: + ereqts |= getattr(dist, '%s_requires' % key) + all_reqts = ireqts | sreqts | ereqts + for r in all_reqts: + providers = self.find_providers(r) + if not providers: + logger.debug('No providers found for %r', r) + provider = self.locator.locate(r, prereleases=prereleases) + # If no provider is found and we didn't consider + # prereleases, consider them now. + if provider is None and not prereleases: + provider = self.locator.locate(r, prereleases=True) + if provider is None: + logger.debug('Cannot satisfy %r', r) + problems.add(('unsatisfied', r)) + else: + n, v = provider.key, provider.version + if (n, v) not in self.dists: + todo.add(provider) + providers.add(provider) + if r in ireqts and dist in install_dists: + install_dists.add(provider) + logger.debug('Adding %s to install_dists', + provider.name_and_version) + for p in providers: + name = p.key + if name not in self.dists_by_name: + self.reqts.setdefault(p, set()).add(r) + else: + other = self.dists_by_name[name] + if other != p: + # see if other can be replaced by p + self.try_to_replace(p, other, problems) + + dists = set(self.dists.values()) + for dist in dists: + dist.build_time_dependency = dist not in install_dists + if dist.build_time_dependency: + logger.debug('%s is a build-time dependency only.', + dist.name_and_version) + logger.debug('find done for %s', odist) + return dists, problems diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/manifest.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/manifest.py new file mode 100644 index 0000000..ca0fe44 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/manifest.py @@ -0,0 +1,393 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2013 Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Class representing the list of files in a distribution. + +Equivalent to distutils.filelist, but fixes some problems. +""" +import fnmatch +import logging +import os +import re +import sys + +from . import DistlibException +from .compat import fsdecode +from .util import convert_path + + +__all__ = ['Manifest'] + +logger = logging.getLogger(__name__) + +# a \ followed by some spaces + EOL +_COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M) +_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S) + +# +# Due to the different results returned by fnmatch.translate, we need +# to do slightly different processing for Python 2.7 and 3.2 ... this needed +# to be brought in for Python 3.6 onwards. +# +_PYTHON_VERSION = sys.version_info[:2] + +class Manifest(object): + """A list of files built by on exploring the filesystem and filtered by + applying various patterns to what we find there. + """ + + def __init__(self, base=None): + """ + Initialise an instance. + + :param base: The base directory to explore under. + """ + self.base = os.path.abspath(os.path.normpath(base or os.getcwd())) + self.prefix = self.base + os.sep + self.allfiles = None + self.files = set() + + # + # Public API + # + + def findall(self): + """Find all files under the base and set ``allfiles`` to the absolute + pathnames of files found. + """ + from stat import S_ISREG, S_ISDIR, S_ISLNK + + self.allfiles = allfiles = [] + root = self.base + stack = [root] + pop = stack.pop + push = stack.append + + while stack: + root = pop() + names = os.listdir(root) + + for name in names: + fullname = os.path.join(root, name) + + # Avoid excess stat calls -- just one will do, thank you! + stat = os.stat(fullname) + mode = stat.st_mode + if S_ISREG(mode): + allfiles.append(fsdecode(fullname)) + elif S_ISDIR(mode) and not S_ISLNK(mode): + push(fullname) + + def add(self, item): + """ + Add a file to the manifest. + + :param item: The pathname to add. This can be relative to the base. + """ + if not item.startswith(self.prefix): + item = os.path.join(self.base, item) + self.files.add(os.path.normpath(item)) + + def add_many(self, items): + """ + Add a list of files to the manifest. + + :param items: The pathnames to add. These can be relative to the base. + """ + for item in items: + self.add(item) + + def sorted(self, wantdirs=False): + """ + Return sorted files in directory order + """ + + def add_dir(dirs, d): + dirs.add(d) + logger.debug('add_dir added %s', d) + if d != self.base: + parent, _ = os.path.split(d) + assert parent not in ('', '/') + add_dir(dirs, parent) + + result = set(self.files) # make a copy! + if wantdirs: + dirs = set() + for f in result: + add_dir(dirs, os.path.dirname(f)) + result |= dirs + return [os.path.join(*path_tuple) for path_tuple in + sorted(os.path.split(path) for path in result)] + + def clear(self): + """Clear all collected files.""" + self.files = set() + self.allfiles = [] + + def process_directive(self, directive): + """ + Process a directive which either adds some files from ``allfiles`` to + ``files``, or removes some files from ``files``. + + :param directive: The directive to process. This should be in a format + compatible with distutils ``MANIFEST.in`` files: + + http://docs.python.org/distutils/sourcedist.html#commands + """ + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dirpattern). + action, patterns, thedir, dirpattern = self._parse_directive(directive) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + if action == 'include': + for pattern in patterns: + if not self._include_pattern(pattern, anchor=True): + logger.warning('no files found matching %r', pattern) + + elif action == 'exclude': + for pattern in patterns: + found = self._exclude_pattern(pattern, anchor=True) + #if not found: + # logger.warning('no previously-included files ' + # 'found matching %r', pattern) + + elif action == 'global-include': + for pattern in patterns: + if not self._include_pattern(pattern, anchor=False): + logger.warning('no files found matching %r ' + 'anywhere in distribution', pattern) + + elif action == 'global-exclude': + for pattern in patterns: + found = self._exclude_pattern(pattern, anchor=False) + #if not found: + # logger.warning('no previously-included files ' + # 'matching %r found anywhere in ' + # 'distribution', pattern) + + elif action == 'recursive-include': + for pattern in patterns: + if not self._include_pattern(pattern, prefix=thedir): + logger.warning('no files found matching %r ' + 'under directory %r', pattern, thedir) + + elif action == 'recursive-exclude': + for pattern in patterns: + found = self._exclude_pattern(pattern, prefix=thedir) + #if not found: + # logger.warning('no previously-included files ' + # 'matching %r found under directory %r', + # pattern, thedir) + + elif action == 'graft': + if not self._include_pattern(None, prefix=dirpattern): + logger.warning('no directories found matching %r', + dirpattern) + + elif action == 'prune': + if not self._exclude_pattern(None, prefix=dirpattern): + logger.warning('no previously-included directories found ' + 'matching %r', dirpattern) + else: # pragma: no cover + # This should never happen, as it should be caught in + # _parse_template_line + raise DistlibException( + 'invalid action %r' % action) + + # + # Private API + # + + def _parse_directive(self, directive): + """ + Validate a directive. + :param directive: The directive to validate. + :return: A tuple of action, patterns, thedir, dir_patterns + """ + words = directive.split() + if len(words) == 1 and words[0] not in ('include', 'exclude', + 'global-include', + 'global-exclude', + 'recursive-include', + 'recursive-exclude', + 'graft', 'prune'): + # no action given, let's use the default 'include' + words.insert(0, 'include') + + action = words[0] + patterns = thedir = dir_pattern = None + + if action in ('include', 'exclude', + 'global-include', 'global-exclude'): + if len(words) < 2: + raise DistlibException( + '%r expects <pattern1> <pattern2> ...' % action) + + patterns = [convert_path(word) for word in words[1:]] + + elif action in ('recursive-include', 'recursive-exclude'): + if len(words) < 3: + raise DistlibException( + '%r expects <dir> <pattern1> <pattern2> ...' % action) + + thedir = convert_path(words[1]) + patterns = [convert_path(word) for word in words[2:]] + + elif action in ('graft', 'prune'): + if len(words) != 2: + raise DistlibException( + '%r expects a single <dir_pattern>' % action) + + dir_pattern = convert_path(words[1]) + + else: + raise DistlibException('unknown action %r' % action) + + return action, patterns, thedir, dir_pattern + + def _include_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Select strings (presumably filenames) from 'self.files' that + match 'pattern', a Unix-style wildcard (glob) pattern. + + Patterns are not quite the same as implemented by the 'fnmatch' + module: '*' and '?' match non-special characters, where "special" + is platform-dependent: slash on Unix; colon, slash, and backslash on + DOS/Windows; and colon on Mac OS. + + If 'anchor' is true (the default), then the pattern match is more + stringent: "*.py" will match "foo.py" but not "foo/bar.py". If + 'anchor' is false, both of these will match. + + If 'prefix' is supplied, then only filenames starting with 'prefix' + (itself a pattern) and ending with 'pattern', with anything in between + them, will match. 'anchor' is ignored in this case. + + If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and + 'pattern' is assumed to be either a string containing a regex or a + regex object -- no translation is done, the regex is just compiled + and used as-is. + + Selected strings will be added to self.files. + + Return True if files are found. + """ + # XXX docstring lying about what the special chars are? + found = False + pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) + + # delayed loading of allfiles list + if self.allfiles is None: + self.findall() + + for name in self.allfiles: + if pattern_re.search(name): + self.files.add(name) + found = True + return found + + def _exclude_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Remove strings (presumably filenames) from 'files' that match + 'pattern'. + + Other parameters are the same as for 'include_pattern()', above. + The list 'self.files' is modified in place. Return True if files are + found. + + This API is public to allow e.g. exclusion of SCM subdirs, e.g. when + packaging source distributions + """ + found = False + pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex) + for f in list(self.files): + if pattern_re.search(f): + self.files.remove(f) + found = True + return found + + def _translate_pattern(self, pattern, anchor=True, prefix=None, + is_regex=False): + """Translate a shell-like wildcard pattern to a compiled regular + expression. + + Return the compiled regex. If 'is_regex' true, + then 'pattern' is directly compiled to a regex (if it's a string) + or just returned as-is (assumes it's a regex object). + """ + if is_regex: + if isinstance(pattern, str): + return re.compile(pattern) + else: + return pattern + + if _PYTHON_VERSION > (3, 2): + # ditch start and end characters + start, _, end = self._glob_to_re('_').partition('_') + + if pattern: + pattern_re = self._glob_to_re(pattern) + if _PYTHON_VERSION > (3, 2): + assert pattern_re.startswith(start) and pattern_re.endswith(end) + else: + pattern_re = '' + + base = re.escape(os.path.join(self.base, '')) + if prefix is not None: + # ditch end of pattern character + if _PYTHON_VERSION <= (3, 2): + empty_pattern = self._glob_to_re('') + prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)] + else: + prefix_re = self._glob_to_re(prefix) + assert prefix_re.startswith(start) and prefix_re.endswith(end) + prefix_re = prefix_re[len(start): len(prefix_re) - len(end)] + sep = os.sep + if os.sep == '\\': + sep = r'\\' + if _PYTHON_VERSION <= (3, 2): + pattern_re = '^' + base + sep.join((prefix_re, + '.*' + pattern_re)) + else: + pattern_re = pattern_re[len(start): len(pattern_re) - len(end)] + pattern_re = r'%s%s%s%s.*%s%s' % (start, base, prefix_re, sep, + pattern_re, end) + else: # no prefix -- respect anchor flag + if anchor: + if _PYTHON_VERSION <= (3, 2): + pattern_re = '^' + base + pattern_re + else: + pattern_re = r'%s%s%s' % (start, base, pattern_re[len(start):]) + + return re.compile(pattern_re) + + def _glob_to_re(self, pattern): + """Translate a shell-like glob pattern to a regular expression. + + Return a string containing the regex. Differs from + 'fnmatch.translate()' in that '*' does not match "special characters" + (which are platform-specific). + """ + pattern_re = fnmatch.translate(pattern) + + # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which + # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, + # and by extension they shouldn't match such "special characters" under + # any OS. So change all non-escaped dots in the RE to match any + # character except the special characters (currently: just os.sep). + sep = os.sep + if os.sep == '\\': + # we're using a regex to manipulate a regex, so we need + # to escape the backslash twice + sep = r'\\\\' + escaped = r'\1[^%s]' % sep + pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re) + return pattern_re diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/markers.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/markers.py new file mode 100644 index 0000000..ee1f3e2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/markers.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Parser for the environment markers micro-language defined in PEP 508. +""" + +# Note: In PEP 345, the micro-language was Python compatible, so the ast +# module could be used to parse it. However, PEP 508 introduced operators such +# as ~= and === which aren't in Python, necessitating a different approach. + +import os +import sys +import platform +import re + +from .compat import python_implementation, urlparse, string_types +from .util import in_venv, parse_marker + +__all__ = ['interpret'] + +def _is_literal(o): + if not isinstance(o, string_types) or not o: + return False + return o[0] in '\'"' + +class Evaluator(object): + """ + This class is used to evaluate marker expessions. + """ + + operations = { + '==': lambda x, y: x == y, + '===': lambda x, y: x == y, + '~=': lambda x, y: x == y or x > y, + '!=': lambda x, y: x != y, + '<': lambda x, y: x < y, + '<=': lambda x, y: x == y or x < y, + '>': lambda x, y: x > y, + '>=': lambda x, y: x == y or x > y, + 'and': lambda x, y: x and y, + 'or': lambda x, y: x or y, + 'in': lambda x, y: x in y, + 'not in': lambda x, y: x not in y, + } + + def evaluate(self, expr, context): + """ + Evaluate a marker expression returned by the :func:`parse_requirement` + function in the specified context. + """ + if isinstance(expr, string_types): + if expr[0] in '\'"': + result = expr[1:-1] + else: + if expr not in context: + raise SyntaxError('unknown variable: %s' % expr) + result = context[expr] + else: + assert isinstance(expr, dict) + op = expr['op'] + if op not in self.operations: + raise NotImplementedError('op not implemented: %s' % op) + elhs = expr['lhs'] + erhs = expr['rhs'] + if _is_literal(expr['lhs']) and _is_literal(expr['rhs']): + raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs)) + + lhs = self.evaluate(elhs, context) + rhs = self.evaluate(erhs, context) + result = self.operations[op](lhs, rhs) + return result + +def default_context(): + def format_full_version(info): + version = '%s.%s.%s' % (info.major, info.minor, info.micro) + kind = info.releaselevel + if kind != 'final': + version += kind[0] + str(info.serial) + return version + + if hasattr(sys, 'implementation'): + implementation_version = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + else: + implementation_version = '0' + implementation_name = '' + + result = { + 'implementation_name': implementation_name, + 'implementation_version': implementation_version, + 'os_name': os.name, + 'platform_machine': platform.machine(), + 'platform_python_implementation': platform.python_implementation(), + 'platform_release': platform.release(), + '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], + 'sys_platform': sys.platform, + } + return result + +DEFAULT_CONTEXT = default_context() +del default_context + +evaluator = Evaluator() + +def interpret(marker, execution_context=None): + """ + Interpret a marker and return a result depending on environment. + + :param marker: The marker to interpret. + :type marker: str + :param execution_context: The context used for name lookup. + :type execution_context: mapping + """ + try: + expr, rest = parse_marker(marker) + except Exception as e: + raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e)) + if rest and rest[0] != '#': + raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest)) + context = dict(DEFAULT_CONTEXT) + if execution_context: + context.update(execution_context) + return evaluator.evaluate(expr, context) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/metadata.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/metadata.py new file mode 100644 index 0000000..77eed7f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/metadata.py @@ -0,0 +1,1094 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +"""Implementation of the Metadata for Python packages PEPs. + +Supports all metadata formats (1.0, 1.1, 1.2, and 2.0 experimental). +""" +from __future__ import unicode_literals + +import codecs +from email import message_from_file +import json +import logging +import re + + +from . import DistlibException, __version__ +from .compat import StringIO, string_types, text_type +from .markers import interpret +from .util import extract_by_key, get_extras +from .version import get_scheme, PEP440_VERSION_RE + +logger = logging.getLogger(__name__) + + +class MetadataMissingError(DistlibException): + """A required metadata is missing""" + + +class MetadataConflictError(DistlibException): + """Attempt to read or write metadata fields that are conflictual.""" + + +class MetadataUnrecognizedVersionError(DistlibException): + """Unknown metadata version number.""" + + +class MetadataInvalidError(DistlibException): + """A metadata value is invalid""" + +# public API of this module +__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION'] + +# Encoding used for the PKG-INFO files +PKG_INFO_ENCODING = 'utf-8' + +# preferred version. Hopefully will be changed +# to 1.2 once PEP 345 is supported everywhere +PKG_INFO_PREFERRED_VERSION = '1.1' + +_LINE_PREFIX_1_2 = re.compile('\n \\|') +_LINE_PREFIX_PRE_1_2 = re.compile('\n ') +_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'License') + +_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'License', 'Classifier', 'Download-URL', 'Obsoletes', + 'Provides', 'Requires') + +_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', + 'Download-URL') + +_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'Maintainer', 'Maintainer-email', 'License', + 'Classifier', 'Download-URL', 'Obsoletes-Dist', + 'Project-URL', 'Provides-Dist', 'Requires-Dist', + 'Requires-Python', 'Requires-External') + +_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', + 'Obsoletes-Dist', 'Requires-External', 'Maintainer', + 'Maintainer-email', 'Project-URL') + +_426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', + 'Supported-Platform', 'Summary', 'Description', + 'Keywords', 'Home-page', 'Author', 'Author-email', + 'Maintainer', 'Maintainer-email', 'License', + 'Classifier', 'Download-URL', 'Obsoletes-Dist', + 'Project-URL', 'Provides-Dist', 'Requires-Dist', + 'Requires-Python', 'Requires-External', 'Private-Version', + 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension', + 'Provides-Extra') + +_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', + 'Setup-Requires-Dist', 'Extension') + +# See issue #106: Sometimes 'Requires' occurs wrongly in the metadata. Include +# it in the tuple literal below to allow it (for now) +_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires') + +_566_MARKERS = ('Description-Content-Type',) + +_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) + +EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''') + + +def _version2fieldlist(version): + if version == '1.0': + return _241_FIELDS + elif version == '1.1': + return _314_FIELDS + elif version == '1.2': + return _345_FIELDS + elif version in ('1.3', '2.1'): + return _345_FIELDS + _566_FIELDS + elif version == '2.0': + return _426_FIELDS + raise MetadataUnrecognizedVersionError(version) + + +def _best_version(fields): + """Detect the best version depending on the fields used.""" + def _has_marker(keys, markers): + for marker in markers: + if marker in keys: + return True + return False + + keys = [] + for key, value in fields.items(): + if value in ([], 'UNKNOWN', None): + continue + keys.append(key) + + possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.0', '2.1'] + + # first let's try to see if a field is not part of one of the version + for key in keys: + if key not in _241_FIELDS and '1.0' in possible_versions: + possible_versions.remove('1.0') + logger.debug('Removed 1.0 due to %s', key) + if key not in _314_FIELDS and '1.1' in possible_versions: + possible_versions.remove('1.1') + logger.debug('Removed 1.1 due to %s', key) + if key not in _345_FIELDS and '1.2' in possible_versions: + possible_versions.remove('1.2') + logger.debug('Removed 1.2 due to %s', key) + if key not in _566_FIELDS and '1.3' in possible_versions: + possible_versions.remove('1.3') + logger.debug('Removed 1.3 due to %s', key) + if key not in _566_FIELDS and '2.1' in possible_versions: + 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) + + # possible_version contains qualified versions + if len(possible_versions) == 1: + return possible_versions[0] # found ! + elif len(possible_versions) == 0: + logger.debug('Out of options - unknown metadata set: %s', fields) + raise MetadataConflictError('Unknown metadata set') + + # let's see if one unique marker is found + 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') + + # we have the choice, 1.0, or 1.2, or 2.0 + # - 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: + # we couldn't find any specific marker + if PKG_INFO_PREFERRED_VERSION in possible_versions: + return PKG_INFO_PREFERRED_VERSION + if is_1_1: + return '1.1' + if is_1_2: + return '1.2' + if is_2_1: + return '2.1' + + return '2.0' + +_ATTR2FIELD = { + 'metadata_version': 'Metadata-Version', + 'name': 'Name', + 'version': 'Version', + 'platform': 'Platform', + 'supported_platform': 'Supported-Platform', + 'summary': 'Summary', + 'description': 'Description', + 'keywords': 'Keywords', + 'home_page': 'Home-page', + 'author': 'Author', + 'author_email': 'Author-email', + 'maintainer': 'Maintainer', + 'maintainer_email': 'Maintainer-email', + 'license': 'License', + 'classifier': 'Classifier', + 'download_url': 'Download-URL', + 'obsoletes_dist': 'Obsoletes-Dist', + 'provides_dist': 'Provides-Dist', + 'requires_dist': 'Requires-Dist', + 'setup_requires_dist': 'Setup-Requires-Dist', + 'requires_python': 'Requires-Python', + 'requires_external': 'Requires-External', + 'requires': 'Requires', + 'provides': 'Provides', + 'obsoletes': 'Obsoletes', + 'project_url': 'Project-URL', + 'private_version': 'Private-Version', + 'obsoleted_by': 'Obsoleted-By', + 'extension': 'Extension', + 'provides_extra': 'Provides-Extra', +} + +_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') +_VERSIONS_FIELDS = ('Requires-Python',) +_VERSION_FIELDS = ('Version',) +_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', + 'Requires', 'Provides', 'Obsoletes-Dist', + 'Provides-Dist', 'Requires-Dist', 'Requires-External', + 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist', + 'Provides-Extra', 'Extension') +_LISTTUPLEFIELDS = ('Project-URL',) + +_ELEMENTSFIELD = ('Keywords',) + +_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description') + +_MISSING = object() + +_FILESAFE = re.compile('[^A-Za-z0-9.]+') + + +def _get_name_and_version(name, version, for_filename=False): + """Return the distribution name with version. + + If for_filename is true, return a filename-escaped form.""" + if for_filename: + # For both name and version any runs of non-alphanumeric or '.' + # characters are replaced with a single '-'. Additionally any + # spaces in the version string become '.' + name = _FILESAFE.sub('-', name) + version = _FILESAFE.sub('-', version.replace(' ', '.')) + return '%s-%s' % (name, version) + + +class LegacyMetadata(object): + """The legacy metadata of a release. + + Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can + instantiate the class with one of these arguments (or none): + - *path*, the path to a metadata file + - *fileobj* give a file-like object with metadata as content + - *mapping* is a dict-like object + - *scheme* is a version scheme name + """ + # TODO document the mapping API and UNKNOWN default key + + def __init__(self, path=None, fileobj=None, mapping=None, + scheme='default'): + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + self._fields = {} + self.requires_files = [] + self._dependencies = None + self.scheme = scheme + if path is not None: + self.read(path) + elif fileobj is not None: + self.read_file(fileobj) + elif mapping is not None: + self.update(mapping) + self.set_metadata_version() + + def set_metadata_version(self): + self._fields['Metadata-Version'] = _best_version(self._fields) + + def _write_field(self, fileobj, name, value): + fileobj.write('%s: %s\n' % (name, value)) + + def __getitem__(self, name): + return self.get(name) + + def __setitem__(self, name, value): + return self.set(name, value) + + def __delitem__(self, name): + field_name = self._convert_name(name) + try: + del self._fields[field_name] + except KeyError: + raise KeyError(name) + + def __contains__(self, name): + return (name in self._fields or + self._convert_name(name) in self._fields) + + def _convert_name(self, name): + if name in _ALL_FIELDS: + return name + name = name.replace('-', '_').lower() + return _ATTR2FIELD.get(name, name) + + def _default_value(self, name): + if name in _LISTFIELDS or name in _ELEMENTSFIELD: + return [] + return 'UNKNOWN' + + def _remove_line_prefix(self, value): + if self.metadata_version in ('1.0', '1.1'): + return _LINE_PREFIX_PRE_1_2.sub('\n', value) + else: + return _LINE_PREFIX_1_2.sub('\n', value) + + def __getattr__(self, name): + if name in _ATTR2FIELD: + return self[name] + raise AttributeError(name) + + # + # Public API + # + +# dependencies = property(_get_dependencies, _set_dependencies) + + def get_fullname(self, filesafe=False): + """Return the distribution name with version. + + If filesafe is true, return a filename-escaped form.""" + return _get_name_and_version(self['Name'], self['Version'], filesafe) + + def is_field(self, name): + """return True if name is a valid metadata key""" + name = self._convert_name(name) + return name in _ALL_FIELDS + + def is_multi_field(self, name): + name = self._convert_name(name) + return name in _LISTFIELDS + + def read(self, filepath): + """Read the metadata values from a file path.""" + fp = codecs.open(filepath, 'r', encoding='utf-8') + try: + self.read_file(fp) + finally: + fp.close() + + def read_file(self, fileob): + """Read the metadata values from a file object.""" + msg = message_from_file(fileob) + self._fields['Metadata-Version'] = msg['metadata-version'] + + # When reading, get all the fields we can + for field in _ALL_FIELDS: + if field not in msg: + continue + if field in _LISTFIELDS: + # we can have multiple lines + values = msg.get_all(field) + if field in _LISTTUPLEFIELDS and values is not None: + values = [tuple(value.split(',')) for value in values] + self.set(field, values) + else: + # single line + value = msg[field] + if value is not None and value != 'UNKNOWN': + self.set(field, value) + # logger.debug('Attempting to set metadata for %s', self) + # self.set_metadata_version() + + def write(self, filepath, skip_unknown=False): + """Write the metadata fields to filepath.""" + fp = codecs.open(filepath, 'w', encoding='utf-8') + try: + self.write_file(fp, skip_unknown) + finally: + fp.close() + + def write_file(self, fileobject, skip_unknown=False): + """Write the PKG-INFO format data to a file object.""" + self.set_metadata_version() + + for field in _version2fieldlist(self['Metadata-Version']): + values = self.get(field) + if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']): + continue + if field in _ELEMENTSFIELD: + self._write_field(fileobject, field, ','.join(values)) + continue + if field not in _LISTFIELDS: + if field == 'Description': + if self.metadata_version in ('1.0', '1.1'): + values = values.replace('\n', '\n ') + else: + values = values.replace('\n', '\n |') + values = [values] + + if field in _LISTTUPLEFIELDS: + values = [','.join(value) for value in values] + + for value in values: + self._write_field(fileobject, field, value) + + def update(self, other=None, **kwargs): + """Set metadata values from the given iterable `other` and kwargs. + + Behavior is like `dict.update`: If `other` has a ``keys`` method, + they are looped over and ``self[key]`` is assigned ``other[key]``. + Else, ``other`` is an iterable of ``(key, value)`` iterables. + + Keys that don't match a metadata field or that have an empty value are + dropped. + """ + def _set(key, value): + if key in _ATTR2FIELD and value: + self.set(self._convert_name(key), value) + + if not other: + # other is None or empty container + pass + elif hasattr(other, 'keys'): + for k in other.keys(): + _set(k, other[k]) + else: + for k, v in other: + _set(k, v) + + if kwargs: + for k, v in kwargs.items(): + _set(k, v) + + def set(self, name, value): + """Control then set a metadata field.""" + name = self._convert_name(name) + + if ((name in _ELEMENTSFIELD or name == 'Platform') and + not isinstance(value, (list, tuple))): + if isinstance(value, string_types): + value = [v.strip() for v in value.split(',')] + else: + value = [] + elif (name in _LISTFIELDS and + not isinstance(value, (list, tuple))): + if isinstance(value, string_types): + value = [value] + else: + value = [] + + if logger.isEnabledFor(logging.WARNING): + project_name = self['Name'] + + scheme = get_scheme(self.scheme) + if name in _PREDICATE_FIELDS and value is not None: + for v in value: + # check that the values are valid + if not scheme.is_valid_matcher(v.split(';')[0]): + logger.warning( + "'%s': '%s' is not valid (field '%s')", + project_name, v, name) + # FIXME this rejects UNKNOWN, is that right? + elif name in _VERSIONS_FIELDS and value is not None: + if not scheme.is_valid_constraint_list(value): + logger.warning("'%s': '%s' is not a valid version (field '%s')", + project_name, value, name) + elif name in _VERSION_FIELDS and value is not None: + if not scheme.is_valid_version(value): + logger.warning("'%s': '%s' is not a valid version (field '%s')", + project_name, value, name) + + if name in _UNICODEFIELDS: + if name == 'Description': + value = self._remove_line_prefix(value) + + self._fields[name] = value + + def get(self, name, default=_MISSING): + """Get a metadata field.""" + name = self._convert_name(name) + if name not in self._fields: + if default is _MISSING: + default = self._default_value(name) + return default + if name in _UNICODEFIELDS: + value = self._fields[name] + return value + elif name in _LISTFIELDS: + value = self._fields[name] + if value is None: + return [] + res = [] + for val in value: + if name not in _LISTTUPLEFIELDS: + res.append(val) + else: + # That's for Project-URL + res.append((val[0], val[1])) + return res + + elif name in _ELEMENTSFIELD: + value = self._fields[name] + if isinstance(value, string_types): + return value.split(',') + return self._fields[name] + + def check(self, strict=False): + """Check if the metadata is compliant. If strict is True then raise if + no Name or Version are provided""" + self.set_metadata_version() + + # XXX should check the versions (if the file was loaded) + missing, warnings = [], [] + + for attr in ('Name', 'Version'): # required by PEP 345 + if attr not in self: + missing.append(attr) + + if strict and missing != []: + msg = 'missing required metadata: %s' % ', '.join(missing) + raise MetadataMissingError(msg) + + for attr in ('Home-page', 'Author'): + if attr not in self: + missing.append(attr) + + # checking metadata 1.2 (XXX needs to check 1.1, 1.0) + if self['Metadata-Version'] != '1.2': + return missing, warnings + + scheme = get_scheme(self.scheme) + + def are_valid_constraints(value): + for v in value: + if not scheme.is_valid_matcher(v.split(';')[0]): + return False + return True + + for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints), + (_VERSIONS_FIELDS, + scheme.is_valid_constraint_list), + (_VERSION_FIELDS, + scheme.is_valid_version)): + for field in fields: + value = self.get(field, None) + if value is not None and not controller(value): + warnings.append("Wrong value for '%s': %s" % (field, value)) + + return missing, warnings + + def todict(self, skip_missing=False): + """Return fields as a dict. + + Field names will be converted to use the underscore-lowercase style + instead of hyphen-mixed case (i.e. home_page instead of Home-page). + """ + self.set_metadata_version() + + mapping_1_0 = ( + ('metadata_version', 'Metadata-Version'), + ('name', 'Name'), + ('version', 'Version'), + ('summary', 'Summary'), + ('home_page', 'Home-page'), + ('author', 'Author'), + ('author_email', 'Author-email'), + ('license', 'License'), + ('description', 'Description'), + ('keywords', 'Keywords'), + ('platform', 'Platform'), + ('classifiers', 'Classifier'), + ('download_url', 'Download-URL'), + ) + + data = {} + for key, field_name in mapping_1_0: + if not skip_missing or field_name in self._fields: + data[key] = self[field_name] + + if self['Metadata-Version'] == '1.2': + mapping_1_2 = ( + ('requires_dist', 'Requires-Dist'), + ('requires_python', 'Requires-Python'), + ('requires_external', 'Requires-External'), + ('provides_dist', 'Provides-Dist'), + ('obsoletes_dist', 'Obsoletes-Dist'), + ('project_url', 'Project-URL'), + ('maintainer', 'Maintainer'), + ('maintainer_email', 'Maintainer-email'), + ) + for key, field_name in mapping_1_2: + if not skip_missing or field_name in self._fields: + if key != 'project_url': + data[key] = self[field_name] + else: + data[key] = [','.join(u) for u in self[field_name]] + + elif self['Metadata-Version'] == '1.1': + mapping_1_1 = ( + ('provides', 'Provides'), + ('requires', 'Requires'), + ('obsoletes', 'Obsoletes'), + ) + for key, field_name in mapping_1_1: + if not skip_missing or field_name in self._fields: + data[key] = self[field_name] + + return data + + def add_requirements(self, requirements): + if self['Metadata-Version'] == '1.1': + # we can't have 1.1 metadata *and* Setuptools requires + for field in ('Obsoletes', 'Requires', 'Provides'): + if field in self: + del self[field] + self['Requires-Dist'] += requirements + + # Mapping API + # TODO could add iter* variants + + def keys(self): + return list(_version2fieldlist(self['Metadata-Version'])) + + def __iter__(self): + for key in self.keys(): + yield key + + def values(self): + return [self[key] for key in self.keys()] + + def items(self): + return [(key, self[key]) for key in self.keys()] + + def __repr__(self): + return '<%s %s %s>' % (self.__class__.__name__, self.name, + self.version) + + +METADATA_FILENAME = 'pydist.json' +WHEEL_METADATA_FILENAME = 'metadata.json' +LEGACY_METADATA_FILENAME = 'METADATA' + + +class Metadata(object): + """ + The metadata of a release. This implementation uses 2.0 (JSON) + metadata where possible. If not possible, it wraps a LegacyMetadata + instance which handles the key-value metadata format. + """ + + METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$') + + NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I) + + VERSION_MATCHER = PEP440_VERSION_RE + + SUMMARY_MATCHER = re.compile('.{1,2047}') + + METADATA_VERSION = '2.0' + + GENERATOR = 'distlib (%s)' % __version__ + + MANDATORY_KEYS = { + 'name': (), + 'version': (), + 'summary': ('legacy',), + } + + INDEX_KEYS = ('name version license summary description author ' + 'author_email keywords platform home_page classifiers ' + 'download_url') + + DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires ' + 'dev_requires provides meta_requires obsoleted_by ' + 'supports_environments') + + SYNTAX_VALIDATORS = { + 'metadata_version': (METADATA_VERSION_MATCHER, ()), + 'name': (NAME_MATCHER, ('legacy',)), + 'version': (VERSION_MATCHER, ('legacy',)), + 'summary': (SUMMARY_MATCHER, ('legacy',)), + } + + __slots__ = ('_legacy', '_data', 'scheme') + + def __init__(self, path=None, fileobj=None, mapping=None, + scheme='default'): + if [path, fileobj, mapping].count(None) < 2: + raise TypeError('path, fileobj and mapping are exclusive') + self._legacy = None + self._data = None + self.scheme = scheme + #import pdb; pdb.set_trace() + if mapping is not None: + try: + self._validate_mapping(mapping, scheme) + self._data = mapping + except MetadataUnrecognizedVersionError: + self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme) + self.validate() + else: + data = None + if path: + with open(path, 'rb') as f: + data = f.read() + elif fileobj: + data = fileobj.read() + if data is None: + # Initialised with no args - to be added + self._data = { + 'metadata_version': self.METADATA_VERSION, + 'generator': self.GENERATOR, + } + else: + if not isinstance(data, text_type): + data = data.decode('utf-8') + try: + self._data = json.loads(data) + self._validate_mapping(self._data, scheme) + except ValueError: + # Note: MetadataUnrecognizedVersionError does not + # inherit from ValueError (it's a DistlibException, + # which should not inherit from ValueError). + # The ValueError comes from the json.load - if that + # succeeds and we get a validation error, we want + # that to propagate + self._legacy = LegacyMetadata(fileobj=StringIO(data), + scheme=scheme) + self.validate() + + common_keys = set(('name', 'version', 'license', 'keywords', 'summary')) + + none_list = (None, list) + none_dict = (None, dict) + + mapped_keys = { + 'run_requires': ('Requires-Dist', list), + 'build_requires': ('Setup-Requires-Dist', list), + 'dev_requires': none_list, + 'test_requires': none_list, + 'meta_requires': none_list, + 'extras': ('Provides-Extra', list), + 'modules': none_list, + 'namespaces': none_list, + 'exports': none_dict, + 'commands': none_dict, + 'classifiers': ('Classifier', list), + 'source_url': ('Download-URL', None), + 'metadata_version': ('Metadata-Version', None), + } + + del none_list, none_dict + + def __getattribute__(self, key): + common = object.__getattribute__(self, 'common_keys') + mapped = object.__getattribute__(self, 'mapped_keys') + if key in mapped: + lk, maker = mapped[key] + if self._legacy: + if lk is None: + result = None if maker is None else maker() + else: + result = self._legacy.get(lk) + else: + value = None if maker is None else maker() + if key not in ('commands', 'exports', 'modules', 'namespaces', + 'classifiers'): + result = self._data.get(key, value) + else: + # special cases for PEP 459 + sentinel = object() + result = sentinel + d = self._data.get('extensions') + if d: + if key == 'commands': + result = d.get('python.commands', value) + elif key == 'classifiers': + d = d.get('python.details') + if d: + result = d.get(key, value) + else: + d = d.get('python.exports') + if not d: + d = self._data.get('python.exports') + if d: + result = d.get(key, value) + if result is sentinel: + result = value + elif key not in common: + result = object.__getattribute__(self, key) + elif self._legacy: + result = self._legacy.get(key) + else: + result = self._data.get(key) + return result + + def _validate_value(self, key, value, scheme=None): + if key in self.SYNTAX_VALIDATORS: + pattern, exclusions = self.SYNTAX_VALIDATORS[key] + if (scheme or self.scheme) not in exclusions: + m = pattern.match(value) + if not m: + raise MetadataInvalidError("'%s' is an invalid value for " + "the '%s' property" % (value, + key)) + + def __setattr__(self, key, value): + self._validate_value(key, value) + common = object.__getattribute__(self, 'common_keys') + mapped = object.__getattribute__(self, 'mapped_keys') + if key in mapped: + lk, _ = mapped[key] + if self._legacy: + if lk is None: + raise NotImplementedError + self._legacy[lk] = value + elif key not in ('commands', 'exports', 'modules', 'namespaces', + 'classifiers'): + self._data[key] = value + else: + # special cases for PEP 459 + d = self._data.setdefault('extensions', {}) + if key == 'commands': + d['python.commands'] = value + elif key == 'classifiers': + d = d.setdefault('python.details', {}) + d[key] = value + else: + d = d.setdefault('python.exports', {}) + d[key] = value + elif key not in common: + object.__setattr__(self, key, value) + else: + if key == 'keywords': + if isinstance(value, string_types): + value = value.strip() + if value: + value = value.split() + else: + value = [] + if self._legacy: + self._legacy[key] = value + else: + self._data[key] = value + + @property + def name_and_version(self): + return _get_name_and_version(self.name, self.version, True) + + @property + def provides(self): + if self._legacy: + result = self._legacy['Provides-Dist'] + else: + result = self._data.setdefault('provides', []) + s = '%s (%s)' % (self.name, self.version) + if s not in result: + result.append(s) + return result + + @provides.setter + def provides(self, value): + if self._legacy: + self._legacy['Provides-Dist'] = value + else: + self._data['provides'] = value + + def get_requirements(self, reqts, extras=None, env=None): + """ + Base method to get dependencies, given a set of extras + to satisfy and an optional environment context. + :param reqts: A list of sometimes-wanted dependencies, + perhaps dependent on extras and environment. + :param extras: A list of optional components being requested. + :param env: An optional environment for marker evaluation. + """ + if self._legacy: + result = reqts + else: + result = [] + extras = get_extras(extras or [], self.extras) + for d in reqts: + if 'extra' not in d and 'environment' not in d: + # unconditional + include = True + else: + if 'extra' not in d: + # Not extra-dependent - only environment-dependent + include = True + else: + include = d.get('extra') in extras + if include: + # Not excluded because of extras, check environment + marker = d.get('environment') + if marker: + include = interpret(marker, env) + if include: + result.extend(d['requires']) + for key in ('build', 'dev', 'test'): + e = ':%s:' % key + if e in extras: + extras.remove(e) + # A recursive call, but it should terminate since 'test' + # has been removed from the extras + reqts = self._data.get('%s_requires' % key, []) + result.extend(self.get_requirements(reqts, extras=extras, + env=env)) + return result + + @property + def dictionary(self): + if self._legacy: + return self._from_legacy() + return self._data + + @property + def dependencies(self): + if self._legacy: + raise NotImplementedError + else: + return extract_by_key(self._data, self.DEPENDENCY_KEYS) + + @dependencies.setter + def dependencies(self, value): + if self._legacy: + raise NotImplementedError + else: + self._data.update(value) + + def _validate_mapping(self, mapping, scheme): + if mapping.get('metadata_version') != self.METADATA_VERSION: + raise MetadataUnrecognizedVersionError() + missing = [] + for key, exclusions in self.MANDATORY_KEYS.items(): + if key not in mapping: + if scheme not in exclusions: + missing.append(key) + if missing: + msg = 'Missing metadata items: %s' % ', '.join(missing) + raise MetadataMissingError(msg) + for k, v in mapping.items(): + self._validate_value(k, v, scheme) + + def validate(self): + if self._legacy: + missing, warnings = self._legacy.check(True) + if missing or warnings: + logger.warning('Metadata: missing: %s, warnings: %s', + missing, warnings) + else: + self._validate_mapping(self._data, self.scheme) + + def todict(self): + if self._legacy: + return self._legacy.todict(True) + else: + result = extract_by_key(self._data, self.INDEX_KEYS) + return result + + def _from_legacy(self): + assert self._legacy and not self._data + result = { + 'metadata_version': self.METADATA_VERSION, + 'generator': self.GENERATOR, + } + lmd = self._legacy.todict(True) # skip missing ones + for k in ('name', 'version', 'license', 'summary', 'description', + 'classifier'): + if k in lmd: + if k == 'classifier': + nk = 'classifiers' + else: + nk = k + result[nk] = lmd[k] + kw = lmd.get('Keywords', []) + if kw == ['']: + kw = [] + result['keywords'] = kw + keys = (('requires_dist', 'run_requires'), + ('setup_requires_dist', 'build_requires')) + for ok, nk in keys: + if ok in lmd and lmd[ok]: + result[nk] = [{'requires': lmd[ok]}] + result['provides'] = self.provides + author = {} + maintainer = {} + return result + + LEGACY_MAPPING = { + 'name': 'Name', + 'version': 'Version', + 'license': 'License', + 'summary': 'Summary', + 'description': 'Description', + 'classifiers': 'Classifier', + } + + def _to_legacy(self): + def process_entries(entries): + reqts = set() + for e in entries: + extra = e.get('extra') + env = e.get('environment') + rlist = e['requires'] + for r in rlist: + if not env and not extra: + reqts.add(r) + else: + marker = '' + if extra: + marker = 'extra == "%s"' % extra + if env: + if marker: + marker = '(%s) and %s' % (env, marker) + else: + marker = env + reqts.add(';'.join((r, marker))) + return reqts + + assert self._data and not self._legacy + result = LegacyMetadata() + nmd = self._data + for nk, ok in self.LEGACY_MAPPING.items(): + if nk in nmd: + result[ok] = nmd[nk] + r1 = process_entries(self.run_requires + self.meta_requires) + r2 = process_entries(self.build_requires + self.dev_requires) + if self.extras: + result['Provides-Extra'] = sorted(self.extras) + result['Requires-Dist'] = sorted(r1) + result['Setup-Requires-Dist'] = sorted(r2) + # TODO: other fields such as contacts + return result + + def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True): + if [path, fileobj].count(None) != 1: + raise ValueError('Exactly one of path and fileobj is needed') + self.validate() + if legacy: + if self._legacy: + legacy_md = self._legacy + else: + legacy_md = self._to_legacy() + if path: + legacy_md.write(path, skip_unknown=skip_unknown) + else: + legacy_md.write_file(fileobj, skip_unknown=skip_unknown) + else: + if self._legacy: + d = self._from_legacy() + else: + d = self._data + if fileobj: + json.dump(d, fileobj, ensure_ascii=True, indent=2, + sort_keys=True) + else: + with codecs.open(path, 'w', 'utf-8') as f: + json.dump(d, f, ensure_ascii=True, indent=2, + sort_keys=True) + + def add_requirements(self, requirements): + if self._legacy: + self._legacy.add_requirements(requirements) + else: + run_requires = self._data.setdefault('run_requires', []) + always = None + for entry in run_requires: + if 'environment' not in entry and 'extra' not in entry: + always = entry + break + if always is None: + always = { 'requires': requirements } + run_requires.insert(0, always) + else: + rset = set(always['requires']) | set(requirements) + always['requires'] = sorted(rset) + + def __repr__(self): + name = self.name or '(no name)' + version = self.version or 'no version' + return '<%s %s %s (%s)>' % (self.__class__.__name__, + self.metadata_version, name, version) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/resources.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/resources.py new file mode 100644 index 0000000..1884016 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/resources.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import unicode_literals + +import bisect +import io +import logging +import os +import pkgutil +import shutil +import sys +import types +import zipimport + +from . import DistlibException +from .util import cached_property, get_cache_base, path_to_cache_dir, Cache + +logger = logging.getLogger(__name__) + + +cache = None # created when needed + + +class ResourceCache(Cache): + def __init__(self, base=None): + if base is None: + # Use native string to avoid issues on 2.x: see Python #20140. + base = os.path.join(get_cache_base(), str('resource-cache')) + super(ResourceCache, self).__init__(base) + + def is_stale(self, resource, path): + """ + Is the cache stale for the given resource? + + :param resource: The :class:`Resource` being cached. + :param path: The path of the resource in the cache. + :return: True if the cache is stale. + """ + # Cache invalidation is a hard problem :-) + return True + + def get(self, resource): + """ + Get a resource into the cache, + + :param resource: A :class:`Resource` instance. + :return: The pathname of the resource in the cache. + """ + prefix, path = resource.finder.get_cache_info(resource) + if prefix is None: + result = path + else: + result = os.path.join(self.base, self.prefix_to_dir(prefix), path) + dirname = os.path.dirname(result) + if not os.path.isdir(dirname): + os.makedirs(dirname) + if not os.path.exists(result): + stale = True + else: + stale = self.is_stale(resource, path) + if stale: + # write the bytes of the resource to the cache location + with open(result, 'wb') as f: + f.write(resource.bytes) + return result + + +class ResourceBase(object): + def __init__(self, finder, name): + self.finder = finder + self.name = name + + +class Resource(ResourceBase): + """ + A class representing an in-package resource, such as a data file. This is + not normally instantiated by user code, but rather by a + :class:`ResourceFinder` which manages the resource. + """ + is_container = False # Backwards compatibility + + def as_stream(self): + """ + Get the resource as a stream. + + This is not a property to make it obvious that it returns a new stream + each time. + """ + return self.finder.get_stream(self) + + @cached_property + def file_path(self): + global cache + if cache is None: + cache = ResourceCache() + return cache.get(self) + + @cached_property + def bytes(self): + return self.finder.get_bytes(self) + + @cached_property + def size(self): + return self.finder.get_size(self) + + +class ResourceContainer(ResourceBase): + is_container = True # Backwards compatibility + + @cached_property + def resources(self): + return self.finder.get_resources(self) + + +class ResourceFinder(object): + """ + Resource finder for file system resources. + """ + + if sys.platform.startswith('java'): + skipped_extensions = ('.pyc', '.pyo', '.class') + else: + skipped_extensions = ('.pyc', '.pyo') + + def __init__(self, module): + self.module = module + self.loader = getattr(module, '__loader__', None) + self.base = os.path.dirname(getattr(module, '__file__', '')) + + def _adjust_path(self, path): + return os.path.realpath(path) + + def _make_path(self, resource_name): + # Issue #50: need to preserve type of path on Python 2.x + # like os.path._get_sep + if isinstance(resource_name, bytes): # should only happen on 2.x + sep = b'/' + else: + sep = '/' + parts = resource_name.split(sep) + parts.insert(0, self.base) + result = os.path.join(*parts) + return self._adjust_path(result) + + def _find(self, path): + return os.path.exists(path) + + def get_cache_info(self, resource): + return None, resource.path + + def find(self, resource_name): + path = self._make_path(resource_name) + if not self._find(path): + result = None + else: + if self._is_directory(path): + result = ResourceContainer(self, resource_name) + else: + result = Resource(self, resource_name) + result.path = path + return result + + def get_stream(self, resource): + return open(resource.path, 'rb') + + def get_bytes(self, resource): + with open(resource.path, 'rb') as f: + return f.read() + + def get_size(self, resource): + return os.path.getsize(resource.path) + + def get_resources(self, resource): + def allowed(f): + return (f != '__pycache__' and not + f.endswith(self.skipped_extensions)) + return set([f for f in os.listdir(resource.path) if allowed(f)]) + + def is_container(self, resource): + return self._is_directory(resource.path) + + _is_directory = staticmethod(os.path.isdir) + + def iterator(self, resource_name): + resource = self.find(resource_name) + if resource is not None: + todo = [resource] + while todo: + resource = todo.pop(0) + yield resource + if resource.is_container: + rname = resource.name + for name in resource.resources: + if not rname: + new_name = name + else: + new_name = '/'.join([rname, name]) + child = self.find(new_name) + if child.is_container: + todo.append(child) + else: + yield child + + +class ZipResourceFinder(ResourceFinder): + """ + Resource finder for resources in .zip files. + """ + def __init__(self, module): + super(ZipResourceFinder, self).__init__(module) + archive = self.loader.archive + self.prefix_len = 1 + len(archive) + # PyPy doesn't have a _files attr on zipimporter, and you can't set one + if hasattr(self.loader, '_files'): + self._files = self.loader._files + else: + self._files = zipimport._zip_directory_cache[archive] + self.index = sorted(self._files) + + def _adjust_path(self, path): + return path + + def _find(self, path): + path = path[self.prefix_len:] + if path in self._files: + result = True + else: + if path and path[-1] != os.sep: + path = path + os.sep + i = bisect.bisect(self.index, path) + try: + result = self.index[i].startswith(path) + except IndexError: + result = False + if not result: + logger.debug('_find failed: %r %r', path, self.loader.prefix) + else: + logger.debug('_find worked: %r %r', path, self.loader.prefix) + return result + + def get_cache_info(self, resource): + prefix = self.loader.archive + path = resource.path[1 + len(prefix):] + return prefix, path + + def get_bytes(self, resource): + return self.loader.get_data(resource.path) + + def get_stream(self, resource): + return io.BytesIO(self.get_bytes(resource)) + + def get_size(self, resource): + path = resource.path[self.prefix_len:] + return self._files[path][3] + + def get_resources(self, resource): + path = resource.path[self.prefix_len:] + if path and path[-1] != os.sep: + path += os.sep + plen = len(path) + result = set() + i = bisect.bisect(self.index, path) + while i < len(self.index): + if not self.index[i].startswith(path): + break + s = self.index[i][plen:] + result.add(s.split(os.sep, 1)[0]) # only immediate children + i += 1 + return result + + def _is_directory(self, path): + path = path[self.prefix_len:] + if path and path[-1] != os.sep: + path += os.sep + i = bisect.bisect(self.index, path) + try: + result = self.index[i].startswith(path) + except IndexError: + result = False + return result + +_finder_registry = { + type(None): ResourceFinder, + zipimport.zipimporter: ZipResourceFinder +} + +try: + # In Python 3.6, _frozen_importlib -> _frozen_importlib_external + try: + import _frozen_importlib_external as _fi + except ImportError: + import _frozen_importlib as _fi + _finder_registry[_fi.SourceFileLoader] = ResourceFinder + _finder_registry[_fi.FileFinder] = ResourceFinder + del _fi +except (ImportError, AttributeError): + pass + + +def register_finder(loader, finder_maker): + _finder_registry[type(loader)] = finder_maker + +_finder_cache = {} + + +def finder(package): + """ + Return a resource finder for a package. + :param package: The name of the package. + :return: A :class:`ResourceFinder` instance for the package. + """ + if package in _finder_cache: + result = _finder_cache[package] + else: + if package not in sys.modules: + __import__(package) + module = sys.modules[package] + path = getattr(module, '__path__', None) + if path is None: + raise DistlibException('You cannot get a finder for a module, ' + 'only for a package') + loader = getattr(module, '__loader__', None) + finder_maker = _finder_registry.get(type(loader)) + if finder_maker is None: + raise DistlibException('Unable to locate finder for %r' % package) + result = finder_maker(module) + _finder_cache[package] = result + return result + + +_dummy_module = types.ModuleType(str('__dummy__')) + + +def finder_for_path(path): + """ + Return a resource finder for a path, which should represent a container. + + :param path: The path. + :return: A :class:`ResourceFinder` instance for the path. + """ + result = None + # calls any path hooks, gets importer into cache + pkgutil.get_importer(path) + loader = sys.path_importer_cache.get(path) + finder = _finder_registry.get(type(loader)) + if finder: + module = _dummy_module + module.__file__ = os.path.join(path, '') + module.__loader__ = loader + result = finder(module) + return result diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/scripts.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/scripts.py new file mode 100644 index 0000000..8e22cb9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/scripts.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2015 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from io import BytesIO +import logging +import os +import re +import struct +import sys + +from .compat import sysconfig, detect_encoding, ZipFile +from .resources import finder +from .util import (FileOperator, get_export_entry, convert_path, + get_executable, in_venv) + +logger = logging.getLogger(__name__) + +_DEFAULT_MANIFEST = ''' +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <assemblyIdentity version="1.0.0.0" + processorArchitecture="X86" + name="%s" + type="win32"/> + + <!-- Identify the application security requirements. --> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel level="asInvoker" uiAccess="false"/> + </requestedPrivileges> + </security> + </trustInfo> +</assembly>'''.strip() + +# check if Python is called on the first line with this expression +FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$') +SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*- +if __name__ == '__main__': + import sys, re + + def _resolve(module, func): + __import__(module) + mod = sys.modules[module] + parts = func.split('.') + result = getattr(mod, parts.pop(0)) + for p in parts: + result = getattr(result, p) + return result + + try: + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + + func = _resolve('%(module)s', '%(func)s') + rc = func() # None interpreted as 0 + except Exception as e: # only supporting Python >= 2.6 + sys.stderr.write('%%s\n' %% e) + rc = 1 + sys.exit(rc) +''' + + +def _enquote_executable(executable): + if ' ' in executable: + # make sure we quote only the executable in case of env + # for example /usr/bin/env "/dir with spaces/bin/jython" + # instead of "/usr/bin/env /dir with spaces/bin/jython" + # otherwise whole + if executable.startswith('/usr/bin/env '): + env, _executable = executable.split(' ', 1) + if ' ' in _executable and not _executable.startswith('"'): + executable = '%s "%s"' % (env, _executable) + else: + if not executable.startswith('"'): + executable = '"%s"' % executable + return executable + + +class ScriptMaker(object): + """ + A class to copy or create scripts from source scripts or callable + specifications. + """ + script_template = SCRIPT_TEMPLATE + + executable = None # for shebangs + + def __init__(self, source_dir, target_dir, add_launchers=True, + dry_run=False, fileop=None): + self.source_dir = source_dir + self.target_dir = target_dir + self.add_launchers = add_launchers + self.force = False + self.clobber = False + # It only makes sense to set mode bits on POSIX. + self.set_mode = (os.name == 'posix') or (os.name == 'java' and + os._name == 'posix') + self.variants = set(('', 'X.Y')) + self._fileop = fileop or FileOperator(dry_run) + + self._is_nt = os.name == 'nt' or ( + os.name == 'java' and os._name == 'nt') + + def _get_alternate_executable(self, executable, options): + if options.get('gui', False) and self._is_nt: # pragma: no cover + dn, fn = os.path.split(executable) + fn = fn.replace('python', 'pythonw') + executable = os.path.join(dn, fn) + return executable + + if sys.platform.startswith('java'): # pragma: no cover + def _is_shell(self, executable): + """ + Determine if the specified executable is a script + (contains a #! line) + """ + try: + with open(executable) as fp: + return fp.read(2) == '#!' + except (OSError, IOError): + logger.warning('Failed to open %s', executable) + return False + + def _fix_jython_executable(self, executable): + if self._is_shell(executable): + # Workaround for Jython is not needed on Linux systems. + import java + + if java.lang.System.getProperty('os.name') == 'Linux': + return executable + elif executable.lower().endswith('jython.exe'): + # Use wrapper exe for Jython on Windows + return executable + return '/usr/bin/env %s' % executable + + def _build_shebang(self, executable, post_interp): + """ + Build a shebang line. In the simple case (on Windows, or a shebang line + which is not too long or contains spaces) use a simple formulation for + the shebang. Otherwise, use /bin/sh as the executable, with a contrived + shebang which allows the script to run either under Python or sh, using + suitable quoting. Thanks to Harald Nordgren for his input. + + See also: http://www.in-ulm.de/~mascheck/various/shebang/#length + https://hg.mozilla.org/mozilla-central/file/tip/mach + """ + if os.name != 'posix': + simple_shebang = True + else: + # Add 3 for '#!' prefix and newline suffix. + shebang_length = len(executable) + len(post_interp) + 3 + if sys.platform == 'darwin': + max_shebang_length = 512 + else: + max_shebang_length = 127 + simple_shebang = ((b' ' not in executable) and + (shebang_length <= max_shebang_length)) + + if simple_shebang: + result = b'#!' + executable + post_interp + b'\n' + else: + result = b'#!/bin/sh\n' + result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n' + result += b"' '''" + return result + + def _get_shebang(self, encoding, post_interp=b'', options=None): + enquote = True + if self.executable: + executable = self.executable + enquote = False # assume this will be taken care of + elif not sysconfig.is_python_build(): + executable = get_executable() + elif in_venv(): # pragma: no cover + executable = os.path.join(sysconfig.get_path('scripts'), + 'python%s' % sysconfig.get_config_var('EXE')) + else: # pragma: no cover + executable = os.path.join( + sysconfig.get_config_var('BINDIR'), + 'python%s%s' % (sysconfig.get_config_var('VERSION'), + sysconfig.get_config_var('EXE'))) + if options: + executable = self._get_alternate_executable(executable, options) + + if sys.platform.startswith('java'): # pragma: no cover + executable = self._fix_jython_executable(executable) + # Normalise case for Windows + executable = os.path.normcase(executable) + # If the user didn't specify an executable, it may be necessary to + # cater for executable paths with spaces (not uncommon on Windows) + if enquote: + executable = _enquote_executable(executable) + # Issue #51: don't use fsencode, since we later try to + # check that the shebang is decodable using utf-8. + executable = executable.encode('utf-8') + # in case of IronPython, play safe and enable frames support + if (sys.platform == 'cli' and '-X:Frames' not in post_interp + and '-X:FullFrames' not in post_interp): # pragma: no cover + post_interp += b' -X:Frames' + shebang = self._build_shebang(executable, post_interp) + # Python parser starts to read a script using UTF-8 until + # it gets a #coding:xxx cookie. The shebang has to be the + # first line of a file, the #coding:xxx cookie cannot be + # written before. So the shebang has to be decodable from + # UTF-8. + try: + shebang.decode('utf-8') + except UnicodeDecodeError: # pragma: no cover + raise ValueError( + 'The shebang (%r) is not decodable from utf-8' % shebang) + # If the script is encoded to a custom encoding (use a + # #coding:xxx cookie), the shebang has to be decodable from + # the script encoding too. + if encoding != 'utf-8': + try: + shebang.decode(encoding) + except UnicodeDecodeError: # pragma: no cover + raise ValueError( + 'The shebang (%r) is not decodable ' + 'from the script encoding (%r)' % (shebang, encoding)) + return shebang + + def _get_script_text(self, entry): + return self.script_template % dict(module=entry.prefix, + func=entry.suffix) + + manifest = _DEFAULT_MANIFEST + + def get_manifest(self, exename): + base = os.path.basename(exename) + return self.manifest % base + + def _write_script(self, names, shebang, script_bytes, filenames, ext): + use_launcher = self.add_launchers and self._is_nt + linesep = os.linesep.encode('utf-8') + if not shebang.endswith(linesep): + shebang += linesep + if not use_launcher: + script_bytes = shebang + script_bytes + else: # pragma: no cover + if ext == 'py': + launcher = self._get_launcher('t') + else: + launcher = self._get_launcher('w') + stream = BytesIO() + with ZipFile(stream, 'w') as zf: + zf.writestr('__main__.py', script_bytes) + zip_data = stream.getvalue() + script_bytes = launcher + shebang + zip_data + for name in names: + outname = os.path.join(self.target_dir, name) + if use_launcher: # pragma: no cover + n, e = os.path.splitext(outname) + if e.startswith('.py'): + outname = n + outname = '%s.exe' % outname + try: + self._fileop.write_binary_file(outname, script_bytes) + except Exception: + # Failed writing an executable - it might be in use. + logger.warning('Failed to write executable - trying to ' + 'use .deleteme logic') + dfname = '%s.deleteme' % outname + if os.path.exists(dfname): + os.remove(dfname) # Not allowed to fail here + os.rename(outname, dfname) # nor here + self._fileop.write_binary_file(outname, script_bytes) + logger.debug('Able to replace executable using ' + '.deleteme logic') + try: + os.remove(dfname) + except Exception: + pass # still in use - ignore error + else: + if self._is_nt and not outname.endswith('.' + ext): # pragma: no cover + outname = '%s.%s' % (outname, ext) + if os.path.exists(outname) and not self.clobber: + logger.warning('Skipping existing file %s', outname) + continue + self._fileop.write_binary_file(outname, script_bytes) + if self.set_mode: + self._fileop.set_executable_mode([outname]) + filenames.append(outname) + + def _make_script(self, entry, filenames, options=None): + post_interp = b'' + if options: + args = options.get('interpreter_args', []) + if args: + args = ' %s' % ' '.join(args) + post_interp = args.encode('utf-8') + shebang = self._get_shebang('utf-8', post_interp, options=options) + script = self._get_script_text(entry).encode('utf-8') + name = entry.name + scriptnames = set() + if '' in self.variants: + scriptnames.add(name) + if 'X' in self.variants: + scriptnames.add('%s%s' % (name, sys.version[0])) + if 'X.Y' in self.variants: + scriptnames.add('%s-%s' % (name, sys.version[:3])) + if options and options.get('gui', False): + ext = 'pyw' + else: + ext = 'py' + self._write_script(scriptnames, shebang, script, filenames, ext) + + def _copy_script(self, script, filenames): + adjust = False + script = os.path.join(self.source_dir, convert_path(script)) + outname = os.path.join(self.target_dir, os.path.basename(script)) + if not self.force and not self._fileop.newer(script, outname): + logger.debug('not copying %s (up-to-date)', script) + return + + # Always open the file, but ignore failures in dry-run mode -- + # that way, we'll get accurate feedback if we can read the + # script. + try: + f = open(script, 'rb') + except IOError: # pragma: no cover + if not self.dry_run: + raise + f = None + else: + first_line = f.readline() + if not first_line: # pragma: no cover + logger.warning('%s: %s is an empty file (skipping)', + self.get_command_name(), script) + return + + match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n')) + if match: + adjust = True + post_interp = match.group(1) or b'' + + if not adjust: + if f: + f.close() + self._fileop.copy_file(script, outname) + if self.set_mode: + self._fileop.set_executable_mode([outname]) + filenames.append(outname) + else: + logger.info('copying and adjusting %s -> %s', script, + self.target_dir) + if not self._fileop.dry_run: + encoding, lines = detect_encoding(f.readline) + f.seek(0) + shebang = self._get_shebang(encoding, post_interp) + if b'pythonw' in first_line: # pragma: no cover + ext = 'pyw' + else: + ext = 'py' + n = os.path.basename(outname) + self._write_script([n], shebang, f.read(), filenames, ext) + if f: + f.close() + + @property + def dry_run(self): + return self._fileop.dry_run + + @dry_run.setter + def dry_run(self, value): + self._fileop.dry_run = value + + if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): # pragma: no cover + # Executable launcher support. + # Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/ + + def _get_launcher(self, kind): + if struct.calcsize('P') == 8: # 64-bit + bits = '64' + else: + bits = '32' + name = '%s%s.exe' % (kind, bits) + # Issue 31: don't hardcode an absolute package name, but + # determine it relative to the current package + distlib_package = __name__.rsplit('.', 1)[0] + result = finder(distlib_package).find(name).bytes + return result + + # Public API follows + + def make(self, specification, options=None): + """ + Make a script. + + :param specification: The specification, which is either a valid export + entry specification (to make a script from a + callable) or a filename (to make a script by + copying from a source location). + :param options: A dictionary of options controlling script generation. + :return: A list of all absolute pathnames written to. + """ + filenames = [] + entry = get_export_entry(specification) + if entry is None: + self._copy_script(specification, filenames) + else: + self._make_script(entry, filenames, options=options) + return filenames + + def make_multiple(self, specifications, options=None): + """ + Take a list of specifications and make scripts from them, + :param specifications: A list of specifications. + :return: A list of all absolute pathnames written to, + """ + filenames = [] + for specification in specifications: + filenames.extend(self.make(specification, options)) + return filenames diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/t32.exe b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/t32.exe new file mode 100644 index 0000000000000000000000000000000000000000..a09d926872d84ae22a617dfe9ebb560d420b37de GIT binary patch literal 92672 zcmeFae|!{0wm01KBgrHTnE?_A5MachXi%deNF0I#WI|jC4hCk362KMWILj(RH{ePj zu``%XGb_8R_v$|4mCL$UukKy$uKZHLgkS~~70^XiSdF_`t+BHjmuwgyrl0Sro=Jjw z?{oin-_P^UgJ!zA>QvRKQ>RXyI(4eL;;wCiMGyol{&Zas_TfqYJpA{+|A`|xbHb~c z!Yk?TT(QqI@0}|a2Jc_%TD|7M`_|m^W7oa+Jn+DSqU(n%U2CKVT=zfVD!rr9_2UOu zth|2c(2Tr9(NA5t<NC8tT~V9-yW`aU+CSkvn$lGJ4S&8-`#yiFwJ+iMhxXsq{t?f! zPq}Iz<MEFt;9pBTU+2#|@4q)lW&T$!@OcGco+(9m^+zAvm4s;*%%&lx3_&=8m}iaH zUtWi&6MyaW?lHn<K}Zoy6w&__n(+=I7JY33Jw5dtkn&Mx{_KBHq_Emz5@t}qXA*wp zqrkWR?J^0TbV1nmsUYNjD{1iSzP@kuRXeq7FvR8I>&2BDL`2=vh9AO<+De^2=$}gv zmS4YS#XaIZf{>Aqgm(N*!QV0b4f^Ln)z=$f!r^I1aH3)=lNe*rKaU_ZU%zJUntKt) z+ln>|cjCo%Iii5`T)$@Jss{o1@0myk4S0EXeFttfQvct-{|_jzNbRiew1NS4Gz_05 z6uzl=d*xc2AbBHRr%#vck#O%NT@UJz5kcY;ANvDFj(j-FNbm)xT=WR+p`nOt_W0P8 zEK0P8OnSD^?h(|A-okg706sq2ikj34TcA*nl=b=?2UD8I&k}qKn1+r<j&QR$c0Wa_ z>28~3R^yR!lj^nQw?s+{dbRh|=(1`mLGGLq2+l*55pQpy9$cP}GL+h0rM8RRhgu4c zx}%OKT7nA!v4FXBT@RT9y41`3IS_AnE*m8XPb*%Q(%Yx&^5HyXQK#aKyQ8%hr8Zva z2W*_ct~S75vx4y|(HP0bibhZgHnoctqFDK`%N-TRsa>Izsz~hz=bl$<ZTV4)H~zHR zg)(FH=$eCIUaOzA3=ssy+pVHfLFl?vHBeu&w*5c~wfd=|Zgy-qy>+9aw}7MCRoLu4 z?|8B~xEgIzq)s2ZjiSAs`QGkO3TmtZ@Y4nkR5g3YCJ4YrK0GB~>d2Sc^UpnOF6;>j zerni!qbjs1!0tswy!f`U&F4=CpFsIO*7*&mOQdwBzVvP_vqp99--U!4_b@T7+#Ox} zrDjpQT~yT4(a7%Ys#?aoR_?U>L)U{qg*}QCXIB7;sw#BqIDasB-7JH5fPu}gXWPIS zND<4lhXTP@P<X`K?L&Y1Sd?Set@1vY?cjXo?vrkdc;mh|4g-?<QgaO|5-d7Uq?AQ~ z0Y6JaUxBCGZPEvtrLd=r(A|>;jFzcwOF6oJwM);=0wVHNLdYC4fjm@{PtPtTw(Sb{ zNOnDY1_8uVB~uyl8T?0MWB86>(JX30dPqQyTtF2zdyMpsczx$tbiOg14l50Lr|||( z26Gkafq+t)m#b$_rAkgmO7on)&}uw3_(JKGdiE4VqgcDVG0(YLN<pETxv)8S3@!Ju zJ9~A#ersMM4f+D2F3%|%Iqk?9?BsCQ0xnd#)Q@7P27K(yd`?D1%$uwhO$S)0M?d95 z;tJLcMv7YV?3bwca~S3*^B+cHkbP(*PUeZHjKppuaTR;jNG#=v`;A0XaLNde5G~DH zLQ|uj?Ll3rCWq>p;tK=<;JJV<0x3P)i8KVWg3Eac>rsLVDD)X(b9NGWK@OJz1$vbe z-a66{&N0e`bmFghcnvo4VhT7Sh;|y%=NJUW0?=J8DgD$Vy!JAHD$&XMht$8~%t)CH z($2A0r~%C<$nlBdn2^oKB+OvMx{@8hy#}!KJ~9kdt8H?dO}!L*hq|=d7P1HTQJKsG z-YPsAZieWo44y{R0`{wmx*mBX$FVm}KAb}pjG(edC(0I+eOnpK?Ir3<07vWPs2Mp3 zJd?n`z!2c5d|o5pDyZkh(T=^TlyD-M0EEmn#i`QgiG+QL1kqO5T%)8SHNcjFAu2Jz z7ow)IdPrDY|2Yjw$P^#@<^t90tdZRlrK^xdo;k77@kDd5kz@4<QjKzeTANvJH3PvU z6hzW-4z(Xps2=DO;#U!VHzv`@;n_9bn%rdM5R`=sfR;X2y>_Jl(tYXOd|cLd=3%B8 zn2SgxXIs(5HS+X{qBZ2wQbH5uW^2^~A3Fd@qobnXcC_&b*k8+wtTt=I2#4QbV&Nia zaCORVf;8m%L7F}MA+YLXUO@@HPZVv+ZUz`_Xf#aEA0kp_X7x#WDLh)E*k?z=T?qTy zj46z*MElivVRKjqNim*W-%yY4jAJ}S9-|qgu%}9W&mCWz-88K3;!x3EcQHduo8>;T z<}1ytevOPhB;Tj=Y^x|+Rb?dH4MFT{OBM3Z`vW0cF!l|NsRAHMBD?U6`yAz2!ShT< z9-?!DM476pBD?8XQ@ouX{XDZBb2O)i!87Bf&v{Q?8Qg|K(C0qZb)Jg=^D?8qRwXlJ zSk6;-xmzX1vs@8uPG&j4vl#F*z6U-M?j%zAmF@IoKf;d^?!a$hbMbb12D_;!V#PHm zied>c=;}+vE<voyb6^}r%FURNEYTYG`%+JS%Za$!rSb~Clc0ppq8OF;;CB+$BPwT@ zh!4f(pt$fE6nE%E+;YScp?raec%#kF4xsP)J2tokDEZj29?brniFD2;`fkEk-_6^y z4IqAhfIW-ZPd;1_U|)bWj>YoO4ep_&UrFY3t+DH%BSCbm)}c6+j0Jn>N^M7BGX#qJ z6Hvk(m9p4}V+0{8jD(zFKS8jtS$hN!lAWsp&^$gyM-<QG(Bet<OU#>!*M^)!*>;{Y z2RXH)(2Qz|-I9wn_7@lGi+H<yK|+S@$|W@I+73*8PJbo)C0E{@ink-`CH+WeP^mC? zb+9wY-wM&mPC^B&YE^YeR=+CQFinnN`A7_nT&fhX_eKM}P0I_`As@<w{>X-NZON{r zLN-{@jx=_OpajgPyckT4HR>X}W~*_(B@UOHAsK8n;iFPlO|esiut|WCQYu~t6fj<k zawg8gU|5L301=YoXD?ETn9ymy_OU9wRVk^-3KqyKdj&t~7eI&FaLqV^M#F)9PO-OF z9KnLf0{k-AGAgN}SFv$LA&H=0{kpBpPL<uuZn*}uF0-lStCUQ&JgCgKs+sPg!LhRh zakx6vH5!UR`D!VR#jXNes#<1sr%cX4;z$*l`qOQ!d;*nYMQo2}wOPuN%U7FGiAl>) zZ7A7er9@~QhpYleL+*4IHdh9Uy-r61t;4`BVB0b5H|XjFr}z-u2Xb$Yy+i=D_OLE~ z0;MY}Qqjc<kN|Z}-jF3ov+_T2?6tb(_^dTU<@jCeZE~~Av9}A-sEZ~nL=U0pR36<7 znXgwk#nKwgfw$JUyTn#)Ix&%Buf@l{x>gX7)p$?yu}|=h3B{Nykj=3dWTl)bl=FyV zFaB@KZ>g*86_$!=YDHYWXZ1JBApDI+mXxDw1;6w#BmuRwo*KgWY!qt+mnT|UgCK9I zcCT7t4<8l(oc}dil=-a|9Y>3fJNBBs)1nsMBH(qB@H#HGa=Z@Zw`e24Uz~A?Q)CPR zG$zSOm81Y%YG41LKOmP74+>Han|}kie>{8YIxLWMV9Q<r1t4e7h*q@~+9y^;11!6k z<aa!*OIL;LON&!po(#qqTFLH28KiN%h|%#U40;TuQ~W^_qn1_4ZX^J92ys!tj!Fuf z@2+m$Cpc#btvi~_Xco&_iu`H&1T)5cs=KW=O>NsrDIu$mJ%1x%wDVWfNNJVEhpc|3 zh|<{B%MwyTV-_!MEj+oO%GFYK5WHeH%PlVXkhT6o9Yn^)FG77w0pSEhKt0qFPf@Mm zI%sR^MfvjyEuW{VR<MsQ+T3lT6?K`F8<Bl>{e{)Yu<_kxh0RM_+2pB$P*)-n{lpa3 z4IK0$s*8<)BpoDNc>CO4YbMtBEl1t!$Efe-A8EOeBDXjfu$m%4sGn~a>d-VTLvC|n zVX*|%P4*SUiX6|X9Vs_EeXJP3P&Dex4S0wYuN}M%-JP-w2qNBccgvayCA`9%`sH?g zv##g2prO2=Q9!+_y4A?Ld{EvB8x?sWt9C>p4@Z&}eiytn&t3^pbEmp6&sKP*X-S^_ z{2?eZ5D-ln@*&erZ;NYWW)g2QVx=!+W?eHppk8YEi_P*0J)D+Lw6V*e1Bsc*93JG5 z{(g5W!TwdvD17@3y{~VR<%0aRUicn$-lu}eR4=xxKj=mISKg$Fqg!H51nmf#wIj<S zv-P`MBeVOK(JzK0etYqolz+f?xXf(z)Bp4*@H|HO{ZLmy2cEuQ!C-X_`plVt`y8gQ zESl!{w6G7$vDg$7O$nG)=T0MTbbD=U(nx7Z)&2m|se<asf`W04+E!CMUL1=_K)yg? z=mLqM7FUe|83j!@NBV1FbL`KcS7l{L_rD>aR4j51QwJY`hM-i$-ET{y*gvDnsDP0O zCPz>eV*i0~afNN|FkUHJhuF}>ST&@g`|VA0LhXeo7oY!Hj+@uq94Sq=m5{At{Rnn| z3O?*^6?3D)F^FAl7}O+MW*{m(DiA&7W*fwqdK%JrD4W3Rr6H<q;muk=Xa@AvS<Ho^ zfFWo(j8-9j_A;0Wvyj@Q+1ck<i-)eQ!o2f!B@09BRH<!|m7P$F4HF9KSxFh$iFwsY zBE6av&k7sKUYcniKsJ)ARaO0hHIap68lU=JLvvAOqUR#s9Fk2^)_}yTyqP1J0KlAs z@*(!@SVYx2L0qM}7n8~uxi(7>voK4KV%Gulgj7C0j3g6R<y9#MGT$yA(F;$WKVR(4 zT6cwfNf+&vA*_wcJ-p!nXc+)lzuWQK+N|?sc00Nh_8j#S(WaK=z;dFcMZMi*2ZVy% z@DWIx01`_vyMml0j>f+uR=wmty#|IOcWtlZvDXk0(5KM?4%Ubt-YN*!Y_ghWnrh?u zpFpBtQ`@W7cE!Sga#we+St8eV3*v<Rpw8yPlkPvROIKUY!vxc!rKznHXw5&Q4dD}x z`}BIV+UoZ9uD=^ZkNa8sOt7<${iVccQ?vL83BVO5Z#@6>HQrt=&(FRjj;Gi=Wps}? z5$vLS<BcXX?{*!^hPOL>#u2^>wX5E&*y}Xu)M6owZnjhR*w`rGk8WcvAVO4_2&`j| z6V!aWOO573WS^Iuu?8c?sdYlR+@?dhYzH`*V>*f@r+7oLlqFtUEagbo@zNbAoeVPU zRWyJKU%?B<6eF-S%Gk{QiU+j59AmgEM9ZAZxaC7AwlD<_QW#T^9SWnyvpr8z!VnVu z*|3U7op*6Q%&Kk$s=El)BC7F>QcZert<8OjG}~6x{2tbf3GP~hAlN1LCaQpTP;KWh z;#sBE7GO~fg(@&-&s@7ldN9C#fbQTVA1lZEpnDx}xtIb0@#%z?Pg5=SCuz#kQuc3v z*48sCZ?kj__0DJl%~JUk(>|f4J=J237=ZgYpeL_R%wi=27`2n>vZ6yTuI`Yo3@{CK zs?da-K8$aBfPD<Yf;6y4{g{(D_uE=^7)5cddLv<<kfz`=L8vMA+9YVpM={A`IMC}_ zs8U{Nke%bObl+>8rHvz%He`x;ZTQu*S70{6jBB}qOd9l8VZX8^G5!~*UMJGBSRF7< zkn>6esRF3+P=sOJsIXx?k5lP)6blRhUc|BvGWVw-yJPRL0O?HEJNC{*wi<|n;VM>R zhr~f^>@FA)1VpqzlOG0X=?^t>v7l7+iZdV)9ebxk+ozn_j=eWh<~G0{0<4+r0myud zAW>$@1oIuYW0>%cCO|rRd-Ge)pB~$MrMGt(EO`md*j@?ogxS=62`uvr@J+PwRs@M< zR)U6DmKC|FgQ{SkEM8`X#dn!CWUBPD-`~au0Bk|-R>#&$#K8ef%CtEl+4ARFW0Me4 z)6_d`>goJHD%IURhb(BzDPpNC&PwuU6Iwn??J2#<S_fV`;Xc0Bsdm-fk|CMq%yyqz z^AF^qkuQx^TVtnDe#6NPU$Jh?5(b{J#}Eh3H8~ny;k8>qHQN=7x?|7NYjs?e;`uF> zLoJt5P*Ws#J8>n}d#Z)kT7X&~h7l8@BF;W5=Z%4Yl3eOs%uF`R5iPxLdWK}ty*3Y& zn{(&q+65OTC=cb}^6@{7OyTB-Q$Q|lI#(mXbL*Yz9rm6Un`k@VLKC8BQRhM;qvD>@ z0;^S|BB5wO%&FdPi???vDe@T7$7x9a5bYx^-iC3Cp3P>K{syyO!zNBOO(tP51WW2F zTBOm-wUA;kk$-0eT7}GftoR7p=y+Ozs%7>UWXZ`(G^k1C-Y2(zCD%GlN|{~C^s_%e zPMM&et#k@iel~tGh+1Z^YG{7gCb#zjMjQEpNgV!yP0W0enkl74%W_DQHs(b?>z&SJ zeA8UC=qO|*q=n<jmdGp}+9sOYMa^A{CSBItEJP&uaBqgu+*?)2iLsU;_nE{Lxz8+p z#M}RmMEfC*`7AwwOGo?nP@xiKaw`0Q@+8>5qz=ln;8%-QK&2+Bp{);KX?uNf(Go<6 z_p!bo2*OT=y%m;&5PCVCHG=2SDYqM$fYU6#z;+Wp3y@Z&#<j^lRz^X0bln&=wML$? zp+p)63%t$8#3aLr4!O;$Vr?&-q?sRjLu#aSgIVhaS)2lDT!N;D(%9Z>P!P>Uy@r7A zBjMc!iS%W9QcL_fLYS*GQMnm%0%F0e6o8<TlY@$XKxeQapiGr|+WoQkhf4M$kcg}{ zh0K07qKoS_N?M@~BgiQB6v{GIN-Tn)N^)2mTj}?)oAZtF5tXi>TB1}7%r8mN4E2p0 zJib7#R@kfq0rrB8w;&f>Gl=g3@_RanoW-u=Rq<)_I3R~awbGt4yDU!kv)z-ZTjFfm z?Rc`i&;op{20Z`;gb%g%bZxj=mJ1bTh>wl@3QefV#jI6h7iitbS*w6(n1d>4o*@em zOfJds^m|m7U@$*|#P>r{wMQJvi-6fCk6Php|Ni$RgRvPzz(I^f^R@N?iuJSe1eIi| zPH>AEtFzS*6vPwz$0wJ!M`5w5g6<#63i=4SM^JTPPjS(6U_xn#ADdWMiLJt9w6EeW znz>Me2kSiQ*=ajwAY8wXVrc(e`eOeOh}N3o#vH^*XXSk&o|)_3FFabjiy??Xrc`vW zyTJ9}Fk2{>k-lEVbQn5#gp<wV5%=9eywl5W1iB!tEi{(3jsu>0cCg(e?0kk+moLx9 zDCnS3@Oec7%Eq=66kCoC;@Q&KR*DFj*uB(DFd-H@4^z|*8cREu<Hx5LEyP1F^5K_F z=rlOb+g>bnNU1(%0yLY9AMJW<(y2BzU8y*Wea_$AhEhP^l}z=XRlMzTZHGYcpTh{p z(g2@eLDk#NR$)J(m3<6^V^2aJ@>#CFb265RJL3}|`iFMYZ*~{`j_ah~B1XR@9r&%; zn(cJaW2lus#<lavl(YOX=`?>__W>TyJf30$i0Tz~_Tp9bT6YR~heol}PVwAG8ciuj znhF2ypv0ZMpkOqm3%}`Bp*fn;jSxD~u-Pl&(^$jrXvA{eu)yls8>s_4C;~+NH?*h< zvrhH~L<V2})Ptaipj<)#m~8<g6HJiGHa6(6NM8+*{<+?{BL^1w!jqMxxM0p!7IiC& z;>w~f%|d%2@=TXV)@nI^k60kb*N9ij@%7>;wgr5c7%bNy2!-Yzvmm@?0!_7{g=gf7 zUXzyoS~^;SpxM}<C_FkV0OiKfa0=0phc~|}c)%w|9Sym7hha;OS2`a51==odmYK`Z z(1W1NhKP5Ti*sa_BVH%74Dkvq${pby$WiQ#JHp2R6ZOXND#&j;W36}&`6Tu_9zCrd zNBB29-op)eQEwN4#h&JgW=D7%0?>fuzw}|+lHWEDiK6|nI>gGgaX}LM%XMiF$ZVl_ zm&`InZ#n1yq_Sm}>IjcUiRW8|W)Ryu<Rfh^Eqo+*{mNeb4eSMayQxC$MjksUeNk^R zW<ny*u==;j;-WcVn*k|K!=igsGY>i4zoFv@pQU9;ZI|F^cn)QST+57pDV{0DLl%GV z6?8glUI>(F&)*Sl1d!a8Isk+oERiJYN}eSp_&Rd<*`G8%&M@ksYGwcpOw`&eY>XV? z$p;4~J1N;LXcI$e!LvO1U;2~B%59mHY!U|XOCdH(W{ShvJ(hkZu_CDD2J1i&T5Wr2 zGY}KsXO)C`7DP79vo5UH^ptjt0J0gE+hL1THdvME$_AUVAy+AP^0jct8C)$uR4hP| zg=e_6AAJ7&MDRIQEHo*$ySY8i5qS&L;C8o&bysnYcsH3vNWUq6k;pF1ij;jL$DQkk zN6KK;+HnO+01X?SNaoU~?((y5Ad#x7cqyuNSC0pCk=^HK3;#yZW!lfwIOaR;-q3Vb zPJ&Gx%I$pC|Aa+je(*UgNs?J*ZXv6~;0rhNIB5hbU_WLkh`%ejyR@;W!vG{xnvr$J zF4Ukbv%4>eBkS+uHaF<n$}*cWL0Oh7-{AzO8T$)EfVmoF8_ke+YHbI|vfBlmj9Cbp z<<6{$vy%2XLjVr4HNhGiAfrNBC7X{~wMu@T_V$F(ya?Yf!rnal_y!DIF2)SW6bTpb zC9B<#PD;2PuS(=B{XTh`ez$)>zq^mq?}20Zt=alyoIfJu8d0-#`w{*KALfteoB886 zujBE|<KZqmAVwn<RwY84Z&6+!2~Q==DDAdhCDK6wa7u*GRV$o`K|tXfS%$m}!ANWf z$p{yykbxv7!Te6xj_rv?SJ8|D##>hS&fV;pzZwQ2%)bXmL3sK@X7(lx#lu+Tb5Dna zAYEz@S1%&c>e-FFT+vdkw|{$e|65G0#|oQ$^p8dH0><y}8F<=Q-`NH^FOHZcU$}0~ z*OBtS$rpyL&kPM+3@y<5&J#$hZcQmgzEEbB`v}%-Eijc;x3bOPF*GH0Uwj1Y*NAIn ztCCT@MwH#C$It$Z>{!DrP;Bf`1gqc`^E#eN0o0>o^e^Zt@(3$**w(;FrFl+eRh~0~ zzx;M=9dl;65uQSC`jnLn%Ogn71na>I2X?a+J1JkQTG6#a!CDdYTt+6hzg90WN<Vfi zvBJ#ZMlf})t+0r;&H`#`n^%V*=K?eGh?7hQL)H0K%X@|P>CDjqtmoUYw`08Pf5E#K z8$H$<Lj<GOBa4_)*{j}-IgBY4o${qVaarUxA!5B-owp?`Qo05Ea9yOh#<9JTrGCh$ zDpYC;H*fH4o~wFcazw4tyLGj?Am*u<@dl%?m8t{^evZN|Y$HdZ+h|=Y8PxDkI||y? z7vH<~$L%nIlspABNf2E@da`qOkfbB~nnPWLiTO@Fo8sleSX0^&!=3;>P@#(#+r{C0 zKQW-buO4ClWJJTpMFR0#SoNSk2V?aay`!1sHZ<^B<Rr%uy|~iuXt)D`M6qwPSxAbF zM$9pC=UABML|132^YU^Q-RWDfAn3Wdp9c*2a2RejwiU`GY9v4l)WtSHPbnO&uC~j4 zeWDv>OqDP8iB|XD*Igf(x-PQh_fB;PFqR*&3evHliCQto#t!)eVL!tB<paEEyH-37 z{eftc17fzKSnK&&)>OpoBRH`T^<j6=R(OQj(7HuxFh^f)*H=5q20Rl@z=*8oFldHi z-iJv+fM?r0WV%LwC|7?dM}KHC%T54d_ivFuP^o@Fd;Wzd3wz*vcH(Zn(E39CT5W;E zoB*tN>QSWY`e)dh1(8C+ox#sQmIZA7vw{Fj$vtURp6$*B@Q=x2yA9D$eaI$+;GBiY zoYb;y5C+_j<;j+vw7;dcB*r`0hQzT6Be~maU+Z8+kXgyisOnb7Z!7HBCB=%!R94t5 z_qDGd;Sbr8JGHd!g%N*~TtYiuf|%=P%d#-o5O<QBro_}_Q5p<UPE?i}HDSe1+d0?$ z3M3LILX8qf$qeoj<sx>~TKAFDV(Y%){MU*_Nb9~~6jotwSG#xzlB;1Zb_Y&hLlnXm zpW32qvMQTw$|ifur_LcQkxkB*UV3T2kVSlL2XOwoZ&1%SWtkeCo;#%TkuBr!dJys( zaW=%wm(DLsNYMJuTrk3*`6v(xGgv%*`Z}wg{REoKcPD6q?nO%qn;RRr*P+K9UDMqZ z{t}>VVVVYA4b5UfWcyc$aO^qa*kf@YSwAwr#p8=SF_h9nt~*&angA4==9sXv+R!YW zLU*kr=S*ZmeLmDpps)mn1U6>@sykDOc*J6|3G^oikg1aO@S$Cr06;$u00g<&gMdzO zpgf}6Rxef4(_#`c>*l47b2e>Fp<=aRJuPN2o1$D4g@PKlrV_!lw8m$6fZF<ocBetc zXt)E#{0k5+JbDcet4~r)q#=_sS&m2Ua><uQug|EPmpRTES>V!!$`?nkx6`XDvY@@u zsafE)Jj?ywnzrP$_x#5+?ZMcvjWn#UU`J(7r(?9nckrF~xvRx-^5#{7I7(d~1asO# zF81%3Yp}b*(ol74Xei4icL6d#0R*d5cM;#Np9Y)A7|fi{7_954?;|b|(_qZ~g!CT* zQsxF#4vlO8eF~sS#fC(L_ES~rKm~usW_5C5-RZ1E&(P-0b0|g`my1ybfh3KOrce-M zz%cw33YuQsD|!>#<Jt_l?;C0OV36kkqMecZdZpncKRwogMC~x;O~V8sFJJwQ+Sb3f z-su{|thA?tWq*LJK!3o=r3YqoxLRhat?X5FB-Tf?WI@AVg4tJq#yT2)M#y<P<mQ5s zE(F(nUazxnun=kx0a>q;hmxZqh_GXC6w1a6oN|r^KVl+Y=7S>_4GJ0$HzSIV(8!!z z*kq=|Rig0ZZ1A`8h*eo@FJ8nPTWHMG)qaU0-$y7SebtoNfTb50Kyd6S!$>(AdlBJ5 z#e5BMuU2%Rm>(T2fKna#PY-nx3=jEDWhM-=YaDxKI`%Zf=;Cc}s+)pDTd8{-N;A!M z$Jc#9PP1+1x|xD>937`)iQZ<DYul|TVNFbp0=MWK?y=79#|~g9RheUt%yCAPsVL~K z8ui8+r2uwnY*YR~`dU55J_Jzg6%5L{d6scjSYFrlQ1P2|!4W2BjL4kv`}?SoHk;=* z>4G}P%7!5eN>wUt@Un%jVaO~)R6RnXO8d9sBH|NAcp(ag#fQehQm+4<;R7KnxQhnD zXE2h=7416PiiwF7{<Dl0=IXK_`kXz4!AtH!bF7Yr0Ck1S3>(BP*u8^o4O>wSWr*BQ zD>DoU_0qZL<tw@4BzpxJt6)BAr<EIZkSd+k*9H4W$uPAnSYnJ5AM>6Cu(C8*sg}^l z&_C=cTa88R7s%F=LZj2<2>%H$7$Hw*Cx_r1>&_`?AEw@&1^j8>ITg>sX4tIccuK9a zMx8gu2`4<S3(+184rxd!A)#G6v}s;WZeycsBqhX*1c4GDuyRPkG&W8iMQNYueAM=% zJ%W$se#EzelvT<&8sU}thshBQ5(!!XkR3rYSF1J&MqtTRf5~WWCG%4*HUV~7!_1&r z<(2JFklNX^h-;NgwnBS??{MfF=11REMN=pOSfO#oEDMW95mAcvG6MQ3^|4(@g#Kmm z(F?3*123-(erX<fi7fL)y*Bi@Q2$6g4>T6jRZF4>`4Q|rW`NC-@2yU~!X}~U4*;J+ zMWQ0EDR8Bi(4ZYx83}|MNy7hYXhA8b6961Bvi#W8Ew2MF@-=7`A1tw92`&cJEkrRy zEQO!IUFsGh8Qw<WZG?~Q{v!t69?HdLlZ~lL-9l|10C-{mU>_`mRaN>PDvxa(h<^w{ z%GhjVEJev4b<1JAT}MON$9w=#w~&$NjXM0~M}4e>M;%YR-M|ZL#v98+5T;;t3(>!1 zGWFKj;-?5FLigZpkhXg$iCsEPwMI7e_w8n*Z-=RAz<vmjfR*wT0TnOn#g5!u>p=7y z6fH-2S4aJ97rkEA$K)jD#^MBAG1adYxX+7|1Ilz3qM?pCa4fd35yX~Wm4r!f+ZbaK zTuUshMwgO*I{F0@@Ntqm55R`ZaxhfXE@J{NTMf-^6DHtXW}@iTs}i$t9yB(Zh3k<6 z+1Wpl^x>O8MdV8-x2^KCDs&i$n||v&N)WVzfPUObxuuR)(pnq9n5}yD%Xn~SIlo@C z8b#>YyAZ=&`N!%-GaxRE)vnsr5AX^Bv@LDjv5Kn17Vt<IcT4*r_2cqTO3`;vd6b@s zd2Jsu$wPS!v0cz5V1w$Swy*gb3zivwg`~@VoywJL(Xu7a#Q|JngOBH2WmA^2X?5F{ zBWT2&wk@|~=+B9k1xbEDs{9kRh_|2Q>0ni2Cg9Oz?v@URPAs{UvQ^NWZ99li2<z)s zvDYwjR3$|fq$y0$K&KVe0uL0wl$0K#^CBJ~CE0M7)QhNv*rYg&9@UR?a?KBBnNg>S zt%7|98>Ykuw}5Dz7Db*x^a0c4;OGR46Fb1#ewb)8->So_C*9BHoI-424{B;gJe|ED z?VN2!MZ6wc$jNdctiT6LTS3Mg6Udm4tsLNtZH|UG+M$-^p%U<S&mT~jS~kUaW5(N5 z<Lx8kZHDo7%y{z{ZwHOHQsZrx@m6lU{j2e|q=dSOD)|{jfLu1B64wbg1<Bt9P3Tty zbwlDqb0Xj*%>za+y_boMh$FeKZd!%Ba18hjG|eh^3HK4rs@M4#vcsWYN(-=S2Y1|f z<nl8+mCJ(I4<dHv-S;mrPC$i3*v@`og!RB+W+R`%bT$<u72^?m`b9@T@!$q<BSdy^ z6+L%Or;a-nT+UzkcsLbY%wKqyo{~!lLQsonSnQ->AdZwv2oO$+Fwye>W)CTE2aT+q zl(K_HLo|gl9+~aIJ_JGWyvBgsnHV{ah8DEV7>1Z-ND1V!^?49VFQV*f5shR0lmU}K zRyWEskTr(pP6Jt92m1^Rimtp@Eg?HrP$@+Tyfpno{rJx0s4h+N^D_`S34SiPoSy-X za>f!bPl2LzIWN;WoHVY_!GCd?F$wJ>Hx0Qni(E4t4UeI5m9%{uspw>F?-K`is`Inp zk?^*Z4dEIof1^geFnYbU2DVb{9B8+5zmAZJdv=Vc9k#wdp<2)dP99a_6!oVxhdB0F zO`0pRsP|6zc`UNQ*1<jkgK;l10u-&}>M^}KP7Yt)GCXPN7zLjsgE^mp7F-gcVc9_& zULm}QE%2U#8ujCe`IKruLZX%;`LVrYAsb7<@*5Jv#;yd7Y5C%3kAsgPJ=qgjXZzXW zFLcCxbO(js<iD?C*7UQT_yvZERWi-hu#`K%HcmAY3wyJE0$avz$-btOwu{M=TrSy0 zx{)|KNKf`~2`U7V85|#qs$#GEpr)?+6n(r9KWqn~OXh=x{y;FW5itz_*f$Sp2YvX# z_O-ihtwT*iF=mMIsMX!K=4-j+394t=QgLjMLd=n<32s*0e<GV=$>luc3VKKwJ&Sz< zkl;cFFd}gPPAE><2yS&WoJRlb+<;({*ZHp^p75%IUj7`S^`b_UqZScQLUlW>R3C>s za8NI5Kr|wtkAI+4!*S`f{FN19_oX$rvzso!@RcV14KFkGn<*QcfG8zRf8QvNqLM`v zSD%$qioK`BOe&}PxZ*v{OI53nYcEB;9jifu`r3|-c&r@;e=L<coe1IWuxg)0z3p`z zpuHgh&^`dr&H)VbybFzi8-*ZU6XmVOV8wLDhGB(G%)$<kW`K0jhS*CqqqnkMU<;#L zK~%nX{98;8Sd=9?8?pR6<<rSnGFiZAp&0M2cqJRgPZF=3L0F8$1S-4<2viwv*4#SH zQ?V^xVRPHx-1Q}dc!o!gk6iO5KQ~}~^A$uT>aFi2p*&~>%$L7@wx4FBc;T5U<$x7+ z!u70S6#zpPHX3FW_>jRXC(VekQ3RL{!jPPyk?<w(sqdqekfUK5fP$T0fkm?{r2c^= z0_+Gl2W_YI5^1ABIu3O3cS!PA*6e&Wk93mB;F8xanMsgI6N0a!0Qe+rOXd^pNejFS z`!0U=%GHA40ai2CUF&E6hL?!dOX5*IlK*bVa^gbp6%>&F$4VcIU`+C@D(OJ*Wken% zwBQ9L@OYpkJ+JSkCL^vB3Nc4h`dQHFG6})u$Pi%nSMX?UX(j!OJq%KXy7lboz*y~a zpA*aAATQ1;Y;Lm8ZQPn-Ls>P&xpPIEr=%P0T*GjTi7N0#!j$G~tiHrHmV<`L2pCO{ zQCZ1F?1#trBG$s51&%~|F&q8xGkPK7B*-p}3=+lJB$R3J!dQf8Z=Hk*r0vcZU}a1S zw<3D!-{*kWBLp8w7dnAg-8yi-q;nq5h`a(3c^VjnJR#RoKU;-fsj9+OM~h^`Vms!* zdt{pcM&HR@u!=-DV!02kohCP@$mN&xny5z?GL&))0uzLcHqRA!DQqmiK`kP9oRE(A zF4ebD0dNa@r!r7eT=AKsArr*H@nCn0qXD-92x<<TyRoxtX+21gbYA%5jb`=Z;&D`6 z?T_AQz=JSk#{kWbbS;omD9sgV<T=vZEo*N~;3O}%2zARR)XB>W1p`0)x-x*=4T9<b zN|twll>5Y*laP`|6&wFmOI3Mgg?jkRrZu$Jz}4R+w8s!YcQvJxHLwD%VbTzg>;sSt zBrQ?T!#_=p!do7WX_l$R$pFfXgD~FSCZVy+%6AweWp?B;b`~8Cv?SBZY_d0QovXtM z@6yJf7M@YhQ4ySMw27d@Nf33X*3GxpX%DrPS?l3$of7I<tYt*z=;RS7H~#}=a@LH? zIQBLhy4OtTZ3)~8Ct<!8l$r4GmZ%humM+IFk`+PQcW@G?03R)bz@n+(Eq#uB$>P`= zL`dg-u4f-dlc8$e4JSl$yy@Y*ha<i{B&Obdhh$0>bh4|9Q+9#>)=dDbw<Akr3&SXM z8<7?=;B=84;Vr}Ar@s&qoZJ<x7K2`m)6o1Mm(}{MvJxdV%>!q}!7aKprPym1|A&~h ze5W*WOQuGC#tSr1Ly6A+X^97n60s}3oTgYe_R6^DFV-7B18rzeJY-p>)V8}z=#Wb7 zLiIe~RxZxn1&e56N85qD-H$Nni8J7Z*dgm#8z&pP&&mDhvmiH*p-t<3M*+;=uxUM4 z+mTe;F_U5Fb+C)r9>dhbrkR0(AxI1}Lz!JYQunE)@J!tWv*dY^?0;f0HueJQ%zP-_ zo2CS?w|<ruZ$5S_cMgD4ndE?fA>0cca{D*rUYJIn+Vb1_GGvr%tQZbU)mH4t82!yx zI}+AQML?!XyTQ*kg3q{&BG#G!cXz>qYP0-oEh_S{mrzgD`O{Tnn`!w?j$&DGQ~)i% z!iE#~FMz=hjhRi2!IJSZ7XulUa6*ua!E|w{DsUG8Kbp}B@e6Txa<;OlH%Uvi91fr| zyvG;WB%FQt0bxc&9}l8yql;^8QWot3pg(R%BuSQZI5^ezGRQ8WOlv5FGTff*2tPZ< zE5Qz=p<>|l08|Vc?t18ecd7R*Ta7kQPrQr-=%3i%qH;kh8eDJe!(ftU{Nr`3SxwTo zi1i=)Xbn7_k6^t(j^-rAifG5=l(+GHNO^47$ax$PBUbxb)hpF;#2o&Elo=ffNijmk z@c?mXKz~2Lwqmav*8)_*{9E65Iu{3*&T`0Q<mV`+6Ql&2-1`IRpV3BOV)D_azDdRE z*~?J{w~V|%U9<30>YBN9((_F5xE##ba8(`-1rKM(=!~l|k*(^c9sol`rgDUF6vnDX zwI7Fa*#Dx1BGlSTl7sDUAJ}`-e4z}sn23deQ#@YE=d^&}GsLSjD!^WALsr(%p9yaE z+7M-?hUMpTl$7j?<Y4$4AX`!DH3`Zav#LL0v<#*ovQJ$}iI|mbp<ygQKDjt;aoGth zxzkk{C_EFwDIZ*s(V<kgpL?meIt$Id_({@8%C;j&GwU`q04GeKlabfRXdEEQX73Mx ztuw&1A7R<0Z-zz49bb<dJ34eJH{vD7g{Zf4Hj2P814Uv!82|M}xB&xO=vh!xirlRm zC+Za)8?Y(T-k75eLmpox8%o22Gjj_3cr*ugI;uMwm(0{1+naIXn>#b}UZvA6z-P_? zKA(Ne(XMWVTL2+#3t&2eYp>)imh94S?4JBPuz}emji17V=W1$yX726HdQbweH+(MK zm)2dYPM=fh4?g>AtYr>h%E1bXcK7G9cc`lA6QwHFijXp0^Qk$31mF_}U>h#$!2H}N zjfOI=!~ON?M4n0PamtgU!N>IBu{calKu-1(L>k9P*f@ebq7PUEfe=kTgN_7U=;PQ7 zl2-68PBtu?U565kV_qk)f>qo2-ZVdMkV1#MK2cBQ;|Qh=CVSc%!O33Ha)$){9P`iz z0APPZuFyn&@=1F=F^J$_wF!C!P#r^zjkN|5iXx1;N6+rygNuWc)3trwaI697$bgvc z!6pp0sMmbWJwz5nu(O_zlOGOC%h;nsTB>4S+${+Gv1!TJ4-m_XTR=SMXX#k=Dma%0 zKk*kH1xd?*W|S_nfqe_I94vbSrh*sXY|HX_(nKU_f5Gk^T**f&ORX>9^eUMJ)cJ5S z?^7}{51=seOFv>p7!Vk*FVbNrX$rd$!w{AMoRGD%Nj&UvcS%FhS~k8K6u>yc&f{B4 z5X5XilTg6XP)DWXQ1MJ$m4g$*^K<g!x8XRl`_iUy0np0Mev26z^D|UQtwKKHLaj8P zJPiL0`GPKvl`qiAm=?Kxf_egH8Tf&h#L1Y%ffuVw%nF$+D;KbpAkUSDFrrBIPeQFt z6}Cp3HWDH&KqpYBI!}Lf#kIYVlLnnMIw8Q7FRm;Z1M0sN4WFFp7Y&ahNOUIka6mNV zLNw&CeFI>3C%~QnSV9Uw1V94RV}R+mu1m*q7=g`NYQ%agBuBr<0F(O$O9?-u#B7oh z8C*(W|1T*h$YIM66yGC7qWy_nir|noq)3fYx~cEK5F@?NTN0kA|AHWz_}_?;|3Iq- zMw^qp(Vsb{B8mML@82UvezYHA<Y&gfr7?dS+d@@Aj8wCY2tkZ2<YI&a1_4Ot8ggos zd7JtM3ld)<*VU|ya^+~_AxOs2Ef_dzO`_xmL?=Ya$v^VO42Tkvix7#~EQ14a7x~`+ zD0Y#0l+JB98oomC1&<^AIX%r#@;RIGLo)IaI=*3y5GY6QRDt=m6tJF>s;|q@*TH3d zMH=FK>^|6#iO=aYpre840xoqlJc<DP;UAS2_}MK4NxWO&XV)9yJ~0nRv#!7k)+_$V z48B@n!|;v~QAML6t!kN;!iPeW$C~%(j7Oz3I&$p7ntu~N9|GGRnsNED5ol;?ras^5 z*khWdWNKM_ZPM<<@!@ogKPZ3b@P5NrXRf-4&mW<_#frC6S=51HKbCc3mqvC8>;#?( zp@V@?3#S6e7x%f1HaA~|teL<L0Yb@PFZ2Vl+bJ)g=L1@8L(>9uX2@urnubMH)4T#J zR&O}E5H>RZs6Vq7tiMQOW&M1dSaQGbXh=mNQ12Y!Z(#Dnkvp-dsk9)^+<ZLV=<RbH zY%UL3tHjaea2q&u{x}If`OkgIA}5>+l<F?+Cq}F^nvFGTGVz)?BmC+^IFL+J51oMX zn-iy!aH|xAyOX_w{UG%;beS&9sN>mt081R?_>c!lsifvT0E7(75v@gL`O#R1QkprL zCjEt(Q&flL-JV(2a<x_bNz-j9br&*ltePxUt8gblU2UJxI7D?s=9m&5d~KzfDH)<q zbu`V(oJ7E04t#5)O?7yT90Y1c<p7<OAx+|-R}m-<!=l`*Bq+eJiXpJ8GD1S6f-OL^ zd}^9LHC4}M?X*yKG;9EfTEXB;-uPn#-MA;=u@w}TW~%6pl%`sHggQq<2jo0(H9Hz; zKL#^rMx8rDN~yD1HA|iAl3LwG$F5qHYUnxL?$ZwW1S*F6RFi4O7)Qfz@iGJMQjL~5 zvq0n6&nVH`UG6@zHYYO6L`TBtoE?(dEE$>v`fESdy-wf^XAL@6s9%n?lws@`VJ-r7 zm>}M&ru6{Taxn`oh#BJkHp@^ot*Jt9oR^xSO>$RvVWCY4&!L}m<J{-d3u&aH0}yQm z{2U-e_dGmW2Da0()ik5+9%`gnOKCCzc^tm=c7Y5gG|~}1j#dx_kKlQG(~yRv8&c=Q zw%`SdK72wnha9(V9)Zf&WZv%BGsIK3za1L9AhM<rjy-QV4l4ADBaTBEP85N)u0>Yu zC%BA9vRY1S9@WuPdLx=NX-?z98&hB`*qGilLUlAQ%$zib>;=iUtLEgN)`p)y{WKgS zG5Oip8+`5O#4;woy6Xg^2@xLSU2v`&xVeW8`Zh~bllPR2rhOi{qLVxzp|H^Y)3DbN zg<~TSu8y#Z?gxEhvhh?$!4TDoBQX}ZJajAbMiyvo;E5r)yXn7W3i6GBlO1$0`2yJD zk7%%bVW>E)Mj1l4bTpgM^ReBCr7eV(KA4Wi(~UWDaRv;XWQcNxGWh9FVxk7h?RDa? zA?Fe^UAT4`Zx7;<yE&IEN^;5M8k|zd5Pt^;;Tpw4oDwHap}++MCaGy{rKwkCXx9?w zq#3|r&N_WW;H7tR)-mGKjY5Ebl7Yq$1C7R*7Bj6qsl-5;W-Yx&6;Kzz&?yjUv7ck6 zGsquGS&H*#qu2x3tT99^TZf=h5DU??8UL{(d=~{)b_%g2G(Q@)9#}1o&~h$JdpvX- zNFT&?30_ECPwX#?B-9>|Dtu;x&CM-oYsRpV39w5i`>T8wLG7g43Nf7&(dQtpA*Izc z$3dL2l-o^W+dh)XZm)A}vj?;3d&onzy~2wjVXEz|Wbdt@368wjFenSKmQ85zmF(wO zWO6OALmS0557hmbQ4Sp}OD+KI#09X1bRwx0&8uXiR-)McwJo?eo6YF2mwj>qMU(!b zdYl96gDgz?bUNZ5I#P)HfrcQ1u|oJQ;Bh}tIhU9tu~b?!44Y<<`!?2nJ$0{Li(=py z+XfSf)o|95r0Z*dU7N{TkUzOr_+4n^Vwy)6=Gn;y7pIc%hanoixA2Y}S%0w(xz}XM zC97Z-#qqOPW({;^^@4oSy5`37f0RG9i1z#wjcIb!B*#or4^Dlz+bk{gaN_Zn{AWu` z%q*s!dkF<+7;s+@94f#LU}>Ipz<2}u4;Tc8B58Yo%r+a@J+Fc=q|b9gIM@RIPCET^ z$SIv48A;q?AkD7~pzm$h!mx3x@EW<|O0G)wGIpM-6zpF~BO+x`!g1x0lDb&Ig$QL< z_{iQ$UaT{fr8!tfKqoN|BLTR~b9cfZWN6uRWzyBOoFNMm$`waL-@!4E`Wn0bB@nF1 zq3aLHJ)sJe?3sn5gQ@bv$dsqwX5BDE9oA^pP2@0V$5f9C*UtVup$EgnliI4M8YHOi zti$XyXk#VeT3FZ&4<h2iNaR=0k&|aCIw%|_Pcnrcmr%lVpu#vFp@iwgg%YOI6be6K z!5-cNkCLPB(fbpK1#9KASMi$ApsNwAJFp8W<l7W}83FQor15t%R&aD2Qi37hjrgip z=@dWdfQdT+=sEzktEDf6-wCjrAN4n@Z}AHO{ujZGh8U&`0iX}!+L=KY0+`i9J)XQe zNBAL(Oi1NFIvVansA)vvC`p7LC5h}qt&LB9h2Msgj)tFNOJ@#Daog$0Nb&Bo_;qZ3 z7?F|L?K2jycQ_6navZG7>GDATbWlG!4mPw*$7?99C2p-!!dsC8djyZUkVnr8Pg)Jg z2%RbcZ5#1Wc5}Mz=JednDY=^tq$s-&<2M$=;uUq^q?-5xnOVeXxY0$NR9;Re!z_;Q zTS%581aFHS><?RGzv~a1V!uYXp2N`aiv4qck~yX#TzBzWX$p1`lmpbs>gHbM0O8{9 zb3|74gIdq?6Ev~A5To+G|50;><KSD7QrmHZ7h<;}377B@(o++~UUhk~lt#s7^J3{u zkEQbhDLlA9Udory8tX3JCN8SG7!*tEF0K-D>MpK#gij&fXb)|h#G(Y|UL}p3lZeEa zF}f@EGLj7HIAhQChh4EJ5N@)}m?n*{d&D$V%E45V$O{T3@~#HVj6x1^lL7HOky+o2 zuHnoOn@<oc;CD&S`yCB4>G>eG6zM5B8m_1321mnH^jz#{7>}p2oA}`h-nWr3jWC~M z&mpJ~K1iW(b5of3t_qipM2;g6;rzyO;M>q-nPXJj05xhCA})jIxdc)k#3G1TCBDM( z_#UVaj)uh;;{3SdtLS)fp3G*6POwfM{%qytj_^xZDAXNtMZ=A#3^@dY?_+-CJI}{? z0dRJNpGDFjia(Cmfn+ITAW7w%4LgODvY%*${x<-f)b;@eqXS%yhCZwYU{D&eqXV~N z7^k{aezq&hr3fJuI|dk;fqE06Xan!f`Pgrx))D?15>;O6_f#YnIQGu%^>N?$h;cC^ z&Sjxuc-`HDLg_fSI3dc#7FDH<XqwyG$N{4qjv|eW25zy9R2?Rt#85$Yw_0w6HaFF1 zB(bC84FN~QP>Y!LG+j<Os3|uiyV3KpDG2Up?{Bq_jm<~@$FdPE$5%TZFF^-58Yc1X zTj|(p;qmu5e!3SZ$?^NejdJ_}@p?J_AlBfZOAqg>I)fAj@<0X4rbN%69BsKArtxjX zwTyVEt9w}hmLF2ee~8tiQG!df*QjBVabyIv89^m=fJU*Iv_3T`&LxV+s134BP<aHd zoTww*+d)0tz7ep>QCrLo1TM=J;g?+U3oDfEL@g!!9Da+r_^7qx4o|$nJ|Jiz3Ab<F zC*5mA@qP*v^W;sb#`IHvfPi-bcvFeW3#f0a1|Y7CfC;IIOLE9z66@$OXX5nWZmLf` ztz{SmQ+A-soj-uF60W1<xxGrb0fEFw)w#gN5W^*sh&A}xr}LsBJVzxw5gXyv3WuoU z>H(4$^5NY2&p{CZM;bVy0xtG527aYp^h5%-s;ce)jr{v?0TV1-0|46w0NmF}!xH_8 z)<GH&-6~@(_%+%<U9LoEj@GV~*;+@#0}vA!CJl>8C8pWpHR=@Jdr>}@UyU3I-ZA<S zq7!|06X2UTfOSDz_yZJJ&={uMIHG)}M`sGLOu(S8k--tpqVl6KPq@S!gD5>MP)Zzc z%<a|S>om9bX>9~(Ns*SPF-M*p02&iMxq0M9Sb)|#&z~M~>ikCoEliB5Z9w^=dRj6U zev3UgFN~47R6cLqeR3IJsI5byQtB0aN{vY8aH}X<pmPBgZr+?q$>Mb?AL&ou=?he{ z&wqfy)l#5rH&_Fg<6S7;lxpD=ZOojn9f)|(<+qh3@B$TZIu%9Ya$5X~KLm57sqfYm z7l;9!O8}MswwVe%+O4<MAU+MtHY{S#<#Qo-0(W(A={Fz;4C$w(-Bvdp+OG$&|1e;U zn&bndDuCd0X3ZFGMAIVl10uw9qpz;h#?Ur@;w@jpPM}#FW~4#XlZHX0GiLF8-h}*w z21gC=X|cmj64%BJo?v#l?qEOv2YUGc2?rgw1nQeV(K%_=1Ek@p+xdLOnFW3#1jT-F zbCSDkxZLb|gVC%g`~cOXjW%XC_3d2+cd(*w75*3bz+nIZOCqr-VQb+bl@nSCKZO|F z6`)5b;0vYli^#*<=mkeL*aaB9xp0@J74ul}dVM#gUWO@MUT&b-ISud!s4T1lq+e@S z%KT)pu8lD=V1QExC!h}k8dhaa2Vvt)iAIUnBpUS{sx86Z;AK>k5A36=#1Z;#3a}6U z9RSbsxGI$^7EP8$t_I-j%Lp|>`hqcLn~ulUfK1<`I2(ex-yx^$MRLg5_Qrj1A6n@V zzQo_W8jtW4{&wOohQHB4kFjw==3YPhcoA9!<r${D5r>oOT&Uw(1#XUkaS6*ixM_5@ zBNMr4kjLQ+ypX;NwzvD31-Ysy!&q*;Ox!PNEQ;|h0BfD=n|=oZMoaOFt!P$qDgHaW z$XFczGoAyMQ`#H2Y$>iLz*hHzu@MOVpO@m5tcEx6`xe?gB)n+5g%;W)2TC4qRQ7!f zZ5c_%Li<0cSYtsY<B%A(6=DCx)@dviLyRw^$FM_(s8O`yXDbopW`Wpec%?NSRz_pk za{~}_`XO2Y5qN`?DEBApvf0J~m<b5RNC%^tqN0o0(cSzw85A1n2RP)Le+pNP-Sn+n zRgd6SRovnVubf$z-xJ$rzMbxRJxX_~9uePk?8U}k3vSN4xzbO!Cj?E9@jlj!&1&w! zD&?}S7URl7qg9Z4i9>5q4F>Z*y37!9i92HZU0dbEC9#e$nKTo$`87&P(B?J-4casy z9lKq?=#zugeq1KBE{i=f06HE)7$lZ~b^m|4Kz0geiT(>@u@hFK@{26FK=#^B#LE+Q zlLfe_UgZ}ykuyxMno0*-d}>Jn1_xbr>8r$9Byt676=#LaxB(v9UUW917ZC+G+3tgZ zbsE876kUs(;ot!HAP7zNhz;5Njwalvw+A)?A|nm2o?@I5gtt;Jd*;_DO4HzBp%&3C zQTR>)F%zw!w}XH+a=b(|&GoZlkgzHumL>0Q|Ew}(of}|tfe9@3I59={Pl0Rs9bzku zva}*UGa(<{>QNQhU=k<dgB&c&K%Pz}&GH9)>|a0SBL_@(o7`%ROx;9R$VqSN939sC zJW?kSW&#ePMN{ayE1GxUSAdhytvbK=ik;$6gaW?_3Fj7#iwk1td7R>h|5Y~$oh~fb zzb329($<>dOc88`i$-ixJn`(R%x{Y<He(LY{|L?EK3qeQw~O*dv4h!)v(;>FF0rs( z`;6OJNbq4Nsl#VTKGC;>JNxySr1YLTVnGuO?YQhKx5rb8EfQSJupgiy6AoSMqCB`@ zi%vw-mvO2f8_Q7@D3P$XWB!D`;%5R<zbg={+8`0J@)2>};9F=Y7o2n?2lgD8Ds5)S z$Bz)-FCTx77a8(#J)Q&dk&wJhKK>{H=IaMz=MMbO<YO5%W3V9-XNmvN2h>O|I#?fy zNmTqjhR3z2&ya`DQZWNIHojdbj>lfx80`G9*iLT6I*-LFxIjrI>sXnU%z+6n995{F z&aXANR^H&WNO`zjw#1e4i_v0s$rbd-ESX4;v=YJdv`I=~yK(dazMwd85qxi*2i`jy z&<n|fd4|&x9a(`!3(iyLFM(`STLQSD942ymWdAl05J#QAs&C<;mbF&n@^UbEn(DLR zIzJNS{{WPHF$EWREXRqUW>2hxN5GHxGy)J*mFm*v%KYV63d$F3j_@ADhVrV^O-tkz z#WrY^_WBD{{>H!IUYJcQN`8v(DoN?lvK2BSwM`{RGv4dz{ecpQN8_FPS6f>0i{yKl z-shJ@lJAew`^*x|1O`0qr)bxg{5<*IMDOEEcAFFF$S7!;C9lvs?#f#ML~tB^1rGe5 ztWq|ufWI3WxPV@kF25UcgxE2805XMr4F?B^8oG+h5H&d@YDkvPFa*tF3@-?pR8vzb zjJaQMDf21L5|R6&QnG}kj4r-ylu)S^`q|aUP)7o0F$ow`CHp;{JmTh4@m4=X;WIdb zjRA{cH5bbZ%Q-sadqn3bu<biYybv~meD(K<7pjo0=TH>9T)Z^FvTIxtvH&}8m4(fI zB~AT1uDFcSz6<Vrvf&6Ov=gt*s*HfRuA4bgA|C;7@9!t#qYGu^oH0XBgO%CVl-g*9 z>z%!6ykk$RuZ%rPDgiiXgq}uc3t-=@us5aZUV9_HN3#f*4LKXmh&S<zC10$&<PuZr zE~QKVf|9Ilv*8Z}6$Q<7G{k^LQ|b(tXq}NRrIu;u=4*f93CEE@vnLS5W!Z$FQ#Tc! znL}4PmCdS~xkS7`*j`1O#S{3=wYVYy`-T%GEAA{FN_S468E6FBa3Y3DcKB_)a`Tee zXwXsVYibL6P+Y`uv;l?NXQYdBaTcNk24x?BuVmY?BS?)L+LVgs8I991=O<gL4P`$` zfLO}(G$bvum&N>;Qjk5Z%`6bbD1$SWiAc0$>D?&K0wJfH`Y#Q$W8d5#C>}>gZZX;) zgpO&r;yYn>_g6NK%gQI0y*LK_4!SH(DO!b|#?+dIwoT8GEVx`wUDQjvU6qxQ+HRHs ziAKuGVS5Q`y>;ymX!GoXzIL`6Z~5FDu{yA&Jq_1I(Kb<66@1XHNo2S51^iUNQBuZv z0p&aCA~}U$Du-PYath{?biz}{j&nuE)OEVB$NjN!zhg~tVPfhkNK9P?QWw5+(~Ac9 z{r>z`|B1NASLyd-r_fLv+QjKT763Y2XJ`|z^<(EHj%~_rK#|r!PQATs+p`2A_2TP0 ze98lN(uavCoX{OGmF`=vV?97Wf$u$M!*9s&?+X$X{ropjbo!^$$u|$=m2u9rm4P?r zf984ZHHZ{k<|qyg<EHKN$9K}5a@tDx=mY6&`=^+WahD{%)|G8TxUkDOdq__!f9IEC zXA1=9?Jo3o6?VDLOKAu1K*^djd`_~fZ9|96h3`kZb4ZuMFZDTpN-3gRxZ|HZX*KN} zB{lM?V4xnavku>l!ik&4>OQ499`zoh4Kp0S5!03G58AxC6GkBK2Q=;*tM!QYtdGq# zc-ImB7&fSVLLKH=uTvU+-s=?b(I7g*b5^w0Rp@otp_SV$`K|krxtWZtb>f_IadNrn zVjp7*M9Gmeb=HEAv6HqEA+;^`F#wf{Zfz`ZgP@^e1r*z9-0$PTEdq=1;jyfcvnszu zycvJj;%^-OoHFxB&lfN1=EJvB8xPkh3kuV+5inE0jsUd;WmMx(h4WPu3>UEdf|XVi z0+QS<n+wIs7$kY<rcosVvWW{z1Qa7(7xgk;%0dK?LC|hTfLAcPM1bW_oLVA)BFK73 zyoUAePPXt9gp3x-2$44-)Kz3f7ThX=0HFkIa5r8ZLg6Sp*oMx-_&I;#%8DF#0|2Ir zVBncIyuP9fA!~g_H{JJ!op$Ssd>hP?UfcD8OH4P?ZQ76*oMM{sf(s?fAr;@o30COK zSFj%f3)v+o<CzzssE~sK*)4>c5L<4@8@0p<E~AxgSCq(t0E>8!VQ6(?bYZ<q1F#*X zt%i))hxFzvkHFm^A6;e=C)KaSvR>cJvm+PsemCRI>a_2we#Tn3FX>Eh>=g`L_8fls zol!A38Uc~^<oO4w^#51}o$T8}rSNQA3+<79!zvIJ6@~(D?K$J{M1|gec%nkL5%e_H zUW#r>RgcqFS^u@j<U~~khmg9Xrp9?@Toe1PbR<Vg&3SdMy2grc>Q;VJ-dLean|oU7 z91Smkdq5zwxElV4DF2sVp<yI$;r~3E9s51hzv(h?5`9Qq*NtVY4v8$UJPo}%;yq2V zzk~vB%=u&BG;n&1G(wHSJcpE7^U=j9s#QG1&!|mfZWM3C?CSCAsDCo*e}jhTe!&Aa zt98Pq-+T7TsFadkfoo{ez3}vKUKw?_h@~aOT;es*B=MMtH?#4E2fbObghd)|l^WmX z?K5dPn5y>CwUe9+G7x9htoRiYgV)jUGMK1P2Ob`HI6K1I@d_En1;dpsC{gejhi55R zCq9HN!SKTzhT-FfTOL3V{j?4ade(LMxHH2Mz8g`FgWkSE9VXoIc)^CpTs+7#vJWbz zIW`<`SeW6)eAZJy#BmNeBp$=<w}|*FBDm`(oKG5l3Mz*z5pM_4aXOs&IMo~t>xlYs zvlxPtj3fLqFvIb~uU>mYkQP&`xkDcvaRP$xAQ7OBE%$@*fu!TH00N2HHzaF!G|*84 z1A}{w$SV&4gD~luu{2Z%M}<i+e+eah_>sl{AG&>@iaqn62@!&OzGKVKuo7ydG&T@2 z17-pCzY{ng!W7KOKa;ofW+O%WCCEaUhb(u)^(czZ*Ol<r-g5=#8rZhr*o&-|xcigM ze}bq0U(=oOs-52!Pa}Z%+LYI1yQ!kD?$gZ$w*LwOtkC4dmpGa~O{@F!=8U)MYQGU0 zZPFE7nvbPi#@2J9Xro+foy~QbB-z9z$%g)6o0KIX98$nBWN$afq;EzTUo<391yR)R zgY@Js5c0pO$JGadJvIvpT5JbaT96>`4r(WNQ&Fs$&|+eXu<^ss2(q927Wy#Gqf9nK zX<mlXlV7)zauVOJf=9>&02xw#J3=tPRAF|5Qd~=Sg<~@LxVSbK*UovfCT&JXlLw_o zd<#cP2K%KG590oaC2{Ice1f1o>BN!^27w1Jim}j~=>iV82LT_XD6Z`gCl}YYi=47( ziP2RF;-bf_b-cw_&PI!kiJu=;HGK5BpNgGbK}>r%C$Z8b=M>V&@Jb4~jlPqVjSmjh zkVaeMHsjbJZUj1H);>d|V{b-&OXAu>es>}L7z@@4TjI846WuF{(q_%DwA4@Mmn46M z@9h}ZB$wwno;ai)x~z!)1#kHb3ygBJvMT+Ky$_`po(y0^oxZ^_7AFvJh{t_lO*(GD zv-}a~i!)}+&69Be5trw1Z{2=mlK6!Bg5~Hx<8H+rpr_!IJLwCSTv5Bx8^?u;{kJFL zW<`*mfPxTB0=t$|2pcitLTKaHQ5?2TDaFTA=%$fdR8L+Dn{XcU1^g;|(aE^UXy6V; zegz{w(u3=h3s2V571H>$B3e$jCnvz^(C@c1P&=Sd0?$Px*Mn?}2Xml}&AUSos?k#1 z>-gRK`fh?VPnKHVTX=*m{yD#|&#C$*->LfY?qpeLlziCso$LBg19CYR`9P>HRFb%V z((r*fOdq_o8aGP<YBJqDNVg8^;w|{D=M-H`b&GjZ)?J5N2UYv;m3et~x^{5m?=eG+ zGVUEL{k@IdhN@KxEJHxsOD;}{D=NW#XbVoRu25-K7V00i5)L?Czre2EX)j)2lTv6~ zM`*2F@LCskhP5Gy01B}yx7(CCR^><bMGJh3tE#K+hRH)eo>X%UO`LxPSY4FE7ftT> zH%-7uRNuO7dJazZ;zENS`KYeqTUq7qL$xN4;?03BTwI+e4MBI)g|$}2o2M3$;gWpe zC&MTy<zQTsjoJDpAqG*DXB>m?!gNlSkvkEc{0Pr^Ob+xBo?H7r!ZZC{u*bJP!t<ji zAnP%M4}63NOC8cxyNj#4#h0<!0M#o8b<z+<ZL~ezj=Etr0AiJu27r@<;wf%cHEyWj z>TMXK_!`ygq6v?tGP=0=@tp?Zxq~xuw@9@Xhq5-!HZDix$WJ5W-7V`!vQ2alv==9u zg3&bkd=NH-wJ|>SAHVoE@`jlYfVW~*hAO%^{swv&FB2;(i>qCdwX#x6#jR7^<3An% zVe|BCTJxa=0XF}ixboJ`ya+%lS4CEK5ZCi>FmHUEc5)JHN|b9Odw=fFFz}?w7|K*q zqFf@HA?$qYubAiL!+Dn(;uED@_Sq*|U2`tT9n1x}16<%DF393s;2hwBT;c+-0A!xF zdDDz~y$ci7`l*Baeg=*Ue!K4<#5ldY@9Eky@l_n~@P+U>Rt8UT%<)7YY6)=wY62OD z(J3OtVj^5&P_2^XJeefcz}J@U`04i$>nl(YWa7k1oZCv0Nh9s&aPIe!iHyT!H@p`b zA1-8MH&7|CU|!9ib~b@Ooop0;W-$kU=CCw+PGbUpb+I@w(%0p&F8-X%7=KP-?fhB5 zPV?tfcAP(R*%AJn&YJmi2HS_HeAuI}^RVCWs8aSkf0ncD{5g+3$)C74fIk<qFn=y) zwfwn+N&LB-{g^*ju$BB7WYzq+iY?;L)vSU)Mdszt4XlJeH?kr;357j%7)k7Eirv#d z!CW3}q~I_f+)BYz9^6L3OA&&7f`VN<_!I^I%7f2P@FO04j)L#;;IAlnm<L~=;C>!_ zor3?tgUuA&$%BU}_!JKwp<sjuF<1rmD1sd2<Mbx-1X{td`+4v*1()*RSqfJ2U^@lN zd9Z_mB|OL|coPqHQt)aX{D6YFJlI9SVLXWCD%#J3aSC4AO6{j9mUZ!<0CCCw%7b*F z1p9~w=~x(h4?&JHoh)N5Ji$r9Jv^92!IyY2hl0=XU@irp<Utn&n|Lsff}448G6h8* zoI=6-d9Z+jOL=fA1uJ=QIt9yla0UfSc+f+^n|QF4f>-lkIR$eO<S5Uhw@jYkqo9Qc z7g8{;5(ySl@NYc0go1zO!Q~YE5JAk0$t?h5*ojqYsyl^W4hQG@R{(+=r0_vbJB+;| zV*b^LvAI*6iI{ChOo2OPdLm{Mk6Aa>T{MHo;8qBVxx6Ar!x!isY*M&WvJ&~qjFO!0 zl$=D&R3j$Kosye~nP|l1xKmt-7^e}F>rTl_#Pl_BtX=qwXd<T5h{<!OOi9FiWW-E& zr+5-EM~s*m?v&C*%pN1g<4!40#Qe&LDRrmJOT_%#h$(lc_!2R7JZ9ZIchN!~<7W?0 z3|gO18li9b6I*TAZ-W+$JFJ_`8O=EVcgW;;$(n})*U*BG>WG(HVA1DEZ6?P~Yu?%~ zar*GEEBPHK?5X$zWYsm!%#L6uvCCsD6V@SwWkMkq-LO<z8_n9E)xYO=HQ5^Nsh$RY zr1Ts-V1~gS%$}iKi36o=##UGYS9-u-+)9@%CqAz@Lp9%GlCB3*SKV@tNt%?=A&zTd z&Rb@grO}8ScFR2$$tky3<wMqt4qR4@RZ8o&vCSv`H+x?KS5>wBzZpbS^kQnFX<ikF z!~t_iMdc!cf}$WQnggMLf(QurI+O}}p~NeuuX@>FX=>T{tQ?xmsnp6+v%$<9%IXr9 zl%|;E{(rywoC6m`vwH9M`~3g^cVOLp&K}oVd+mAewNKi2xb42U3z8?SeoN5BcSAJa zgFpm2c5#<G?boF^*!PFSN3h+)_}@kR+b|?3S!|#L{>4LBIhzlCi;kU+LmqpAuFUcd zDl;uwjp%XjCgRF&VeDjY6hFrPy~+NaDd@_i1Y51*Mi%U#+>6EqyTPzy9sAa?bd-JD zx%JZjq0)a?uxR-P9qq-Q**JXa;js@phdp60{foo{7O@;=K0cQ>#*YP%1ZaB*OA)o9 zGj;J`w<Qtoh<5Q{T#4af->V|uUlBR-w8F3Q<%VrDxGt6`JYC^yx#q{d$BhVL!#!LV zSGXdM?~&#wfc=1X0B->{0bT&C131E#oh}T!|1?Y|Oef4UFwej&g;@&oJk0Yj%V3tl zEQeWM<XHsLg-5AJnZXT7qP+o)0UZHcFi5}_7gFr{u2HYsP^Miu0(KaFaZ_}8(Y(Ip zdLH;!=0W}6&#f;<x=SBKD)QnN;B<eyA}%9OE@^oZz&u$FT;PMAm#@bAJAgBQB@rHN z4=o<-VgE^S@2uk9D=twJH{DNVUj5{5KdW+Kv5U{;F8)9PDAe=pClC8s=B#Pa7}T;Z zArQ9(2n_+m0LB9D0!#yB0qg+qx&?UM0;V5KKbVbSHiqd76N=iG`M~sn=?&8xrYB6# zs(GXF=yAli4zLNZk8vA$6X5|4xa5WU2DL8v0NUV3v#XMKMnTg}4x}#bWRbA?FTuTX zZdjihu36a5a+X;Xt@C#=9Byx@yHpR_OJ$E;s0p4`SE)K3A>{~pd;V#w|Fh`XVHXw* zA#t1PhqxDvsRZoYT@-Sq;_df}w{rbWVRU2lr$efW(+6cpRh&N;MWD4~%?Y)M)7&xD za{dYI0DIykRFjrD=;_|f<v)3_1cNJ!%c$A;eSfr-^`FF)$g~{~LE@D1%(ebl{nEw; zVDj3I_*&bUKY{$|i64Es1Fnwx{V!pSsc(!YCTM=1e!<5BwfhcS*Oh%{`g=Ye(cY7A zfUFjsu?=A&HfJynP5lzJsx2n2Lx8KUrsRm)nNTlxsI`e>cbYqwDcS(M0eH8CI!C?; zlAti{2zRq`otWK$w~68!{*;WCvnMzXYxhDGWnreRB-Vj@a7|bkb$VG_55cW2j#Zq& zz8Tr$?26Zt*WV^iYxq-g^V=kJ4S!1NzD-is@CQ?XtlF{Cv{;Q3PC}>s{F7Ly{|vT$ z!%y03LoZbq%tH5t+7fgmj=Y6Nks61~?U%iAzuV<{xZmxvr|lNUh`S1-KPeo17wl~V z9V3zoqYv&KoWve3Z8|&Z2ZEirA<9v|Ctf_%XW!^!^P4%MkAb0%_z8t!4ZUUfv68Qx zrsuIt;^jKe#W-5Y*-3G7^vQ8J{x;Fu0i|-dSqd82&`Wz0SnXDBRndY<I0GjrW;$3n zI0?6XUVNN;FANo0{lSIGTwiOc{8Ss2$d-7i^xRQpBNf|G&s{kNbWjXtTC@-ZI<5p< zE*k8KDc)>boO5+Q*c`$4xS%6BLtf(!cf8;(Rgc|4yR%I(Tzwp}6$oQB*mg4%Yr}S+ zvb|lmwRYPn-D8S+zNSkpmF!_4>lmOEM}A)Dg>6n)%3Q0E3HRofLJWU7Tpg3<32j+V zV9gB5RiOS=lX`|%p0V4hR+=B~zQ$=NZVXEEnYMv)y81Dcsh?4%RAItI5+|x$_0iTL zl{hc=7Ci2D9)wSgft+*#(rV@sdV16zFQ~7Pa%&cPQCjka_wgOO5$v*K_IJjm0`@ch zl_#lC+~P2?35~B9T_YJ2w&(FcqJ2OZvIB#Dr)~bUbr2g|@Nx>(rPAHa&c0*7KIG4| zm2gr!!c6(<$bBy|3fecPEvCa-Mj}7ww^e-)srVkNzK0p#Ye(S?m5T2)ixwlotc`)) z8vfuMv$oqEiy?#i)~8=<Fnr*eG`f~iZz1+;bjAq1quQR<tSI_eY#LN$md2*JL5~h% z_PT&8v20k7^A*A@N_wmzE<xc=>urb#?rkJg9G<~Tvo*wuE|3_yVEyTga)fqJxF|bJ zZ{Q!A9!@Gp3PQz>R_lU_p*_b4RaBWwe#Gc+df`o1Wy0GiI7h{E3|~1u<Nc&KCAZ6c zgzY@2`aa+gr+W)M>!Mf3S>FofCcCKI#FsJZebMK%vNf9bDK|z(mkMJ(hQgT9N?{Bn zb>eQ<&hMuy4P@rx4V~Ywv<;yth3+K>(OWdIa>w<3yKp0r%?~}|pEYC}=*V<{rj?R5 zj-La5F>Uqn((lm5Mh&kKR*#{!67JQbE(falE|?2>MJ<PjaObm6S`1WJL|qwMoCIqm z>5L#c8YRVPu+xa)y&!XLwO?{y0F@#hw#I9CZ{Wn;$|$U_eK_kOs9yiR^e`k?9T;Uj zqqc6=!*q;uRUQh~MEx#W>OJvxdLg4wrDET3NgxWSTLktipi(og6!D|LLjjj<Qr}v< zRK#i-<E)3Ne(oh{iTg)peK5v(`Cs^UE=8Kg?IPTW<h%zK4r~<Y&(h!wz!!Fqm3-}- zQpLWJW)JO4@9VU36G_kqvnsDa@x?VLUE$4$y(9$Jp!i~L_~*V8y{#b3+xc8CtR*;( z5O=3H*`_qGSsMo(&+!d7HzrMZoQQMwd6#2XA8u<ll!Co>x;dJwV60`hRtMUZ4QM(G zdVY(hU|S#c8;IY&SfS)Z>PuKuhyJlv&Sx<P2sPgK!_awuJ6_p<I^acHPQDUX)I!tI z=VAZ8)z0ss8lsQC`+Em36|V9}oQsQs@e93YR_IS~vvq*bT|C6iKrNj^8JAf&11qCH zjCr);mWca8SRd$(F;Sr^)#*NsNp!3yj&Y7g3yj<`<v-#M1aO0FZO=SY{!)B6zgrK^ zSkiIr;}D!!F(XyegF9m!9<pa`$Ir5f8F@`5jHdj%;5+DNt4|+=nkhd9-?B*y%EBte z5)~K?aY1K9Ld^pAwne9|u)u=PB?Y7hr``&tqK;fr&#{?Q_SgX>4%`J%&;nl$FOR+U zIXE-XWJyfV#iP$Jj{entS0Aj6@@PQGP}AExabu&OA_R*VMNBi`1CMCz=&}UuGu^u$ z5yNjm80@j_Y&v`*W7U%3KRj{NMk+)~ZowWk%@cNrxcH$`3l65!Y86GFN99;l#E4>X zZh$<|Lu)g>+HS-F2!NybirN_LjX59VC?HV|0oG~CHOcY1@a9lSJBlbR9y<#QC_8;O zlTD_j7d(LHHqtLl`COl^h?A@7m67fVKVQE}#4oFWjKs~fbR#}w0pph{_F_9?>W>wz z{_eKcrma1oV&)1sy^~r86f*9Gn@L|`5mVMZj+DyI`Qq(ha!Qcmq^Tg1>8MEEbv&)N zK?Oiep>lWTRq@<H;X(Q|Y%poiSEXlKbP4m>#olmtG+5F|!*cN`Q%^^O!Z1^x;<J#Z z9`8{!`%pC3;4^O<Wd?_#h^VQ6lZl$7^@Ylgdw+)y#|J$w1Sml$Di{J!(B+ZSen}(f z+*rj-%li##HZ(l;i29ZY+#wXP@QQ4NG5x2wEL;T%fSQP+f{yTwJXAI{XJaUnQ~ul( zFM{@%mIl#ocYvx8pd!GuC>>-M^SqyiI&`-%LtT&_0yq1576{<3VNQ`H?vsdosA+2> zkK-O6Y53cLe{;9Z%+<8|<5LR#9EvQDJ#L#Bh4!0L=<Bg(;Wk=aA!V=qS;|t`X{kn8 zBJEr$8%)ZmHs7IDe_9!5KG<kkL^0F}b0O=JPF9fPAtmfvZ*o&o@9_~y!*z8e>YC(i zK!ujQqsN6YW2TM9YFklJX$cBsQPB`Y8?aNI%ZzdCj2WYA`6xeWK{qVuxGDc(y%ecj z1sQu{it>9ga7|fj_3_wDk3q+CKPbWCM1Mr1i8gE|I255;7Hj2JWpq8Tqa+x(FeH`C z$jz*dWY0cE!N-_N@zlPa(u){bCaT77S8a%}rQ5eDKh`c#jL}yWK`01{UC!2ny<F!w zycPzQ1nb3fB0k5JbT?`nR^}EA2vx@9^=YnFbo`wSRrnSR-wdyIv)ViB<4}kMsH%d? zQ@FrzlJiR|J7(0c!LD~ZcvnM1>eu)Riy#Q=+y%38(>m7!s%%={qI-L+!kcp-UT@@3 z&x+QlZCp34>nmV!&WtjoZ5-+esf;;NORT0tJuksY+r<6_qa{sF(i97Oou)?43(H(- zSyPpko1C9lI6LpgYst}T>Im`jq>hk};+!9vU1;!v29WM?&KTNZ6zhM=!ZQW+bkV|2 zeB4fR8oPfnQf#JHcyMtN?pVC5BH5Y<`xLGkVL}n6`bDu9LVYaQ7U`&s(J!{c<34B` zX3~7zyh;XQKQ(tQF9^g)W{HrvH}C`JL)##u*l#>g+8Wq{J7Hhd2OEQ(xv-_z+)tqd z!v;-i<%PA4dEpySF!2KF^{NUcHqb^LX0A!W#5(25bAh;~7eCXm*iu;VIKI)<3~-La zr`~HS#~MVQe$WmICU_>+P%x3`qF~}Ewt@f06ii^-Z-s&hb&kJq^AQrD>wDlC$VxR6 zuhdmXdUwFmP%=>nD;FgbTk=+87^f?la1^}-pVN2LF>T5B-U0hG@10K1NtzB0G%)#R zG3HIHJ<dh(#4E3GW#6u=o=|Ej3e`DegVQ`1YVe*sF8&@>h^~5K2vtw?4A`So2Q*e^ ziQj{39i^$_->i57!<xcBt$4z|o~L_7aSvccg%&kvo?yI<;jFWu*c<QKq2Q}DPyC2! zj+!)2d<y$YWe3H3=&feW6VJoR&^+;E#k;xq0lfc_=7~)BxxVI!X!?NWiEx_GJTZVK zG*9%R3C$B-XwHEG0h(h?`7L4E*HdI*sB^VNO6iKGd*UH9k?7*rtb5||*Q@ECc&NJW ziM!#W_)TmxHgr#Hb;Eo9Xm_N^tG2l<x(3}78_>g7x+i$R6(J1W6LAQq9kKq8>Ylia z&b2yyeI4Bs@4=7KJ;A=Ip?l(0;7Z*S+#s#%G`L#H#dUN~+}R3|8oDP~qmlMM);%$o z$yL!k(O=U&(d&kEPxK@yTGkhL#CsLx6Hh>0`M6@<!>N={P@6XNZK(W%@(Bsz?PX9t z@hT9d@`*WAKG8`jpZErDx&i@>7g`<n2Z|?-qvUab6NUYUTIg#ko-i16<BBJ~0zW;j zI0lzF;>(NcfCxR4G<6la4u%@^Ppm{%{M$57ti!pZ3e6L&=`p`ip?QKS-MHonHj)@h zvXoq{d4f?D{VB~8D!S`wo-jNt=bR_hSU@$!H8fAKBGDB76c(}J*0oMpb*&TQ(FCcM z;%(%JmI-?c=&u9hNEaGctrNZAe~I#NZLJdx;m6QA(UkH3HLVl3K<h+PrFEj=#Uu8Q z#r4%r=rUsnhbpgstan1GRJb9%6Rhu*-U&@GD)df}SAVQ`VhTh{*E=!xD!mhy$P_!K zMRdgzzXbec#S<)t|3SqQr2LwSCz@f!riuy$L-7QAel;ncX#T5FuT)n&!E~xBo_On( zs*zt$@dTAfD8&;>*My;XVlix$;)%Rw$Vb-fR6IdjDxRR}*ye(1rQ(Sk9DuNIV_a7& zo?w8giYIU+4C^2@DV|V7U8Q*98*Her!Zo{6yP*_Mutsu@$Hf@-^?b!#XLZFBCau8s zxB#USNnoe0dITc{rGuolsh|k>)X>GQri$Xt6pjzEBHiyfi@0NhMWh1W1vGrtB3c5b z03L!{)dgQ_`t}UK?eiB8w%zA=r=2LpFneEiUB}LG58|YZr~mFQ0*ej>qNG?G&ct%L z1uFyCQi+M9c$}asch<qAhW!Bc9PYI>bYh#LJ_>d0b$nhDg>}iI=yD9ec`%KNEx4U@ zudR_b)<T)86XWcPFyl%NT<a9i@7S%0^MMIm&uu)-+XI6|e}v#MBwp`?6(Db_TW;Yz zjCpc9M#8Vb)JDRN-HyY>Yfum3oImz4@fH}UntWdOx4goivj<*F4ylt0Mg7%D1zbI% zshWi9xnbQs?Wdq>GRArDO)kSoDw4!rM}0KRN$k&AS5mS5vBJ?OOPV>mR;JKfOH@PI zSf%s<YB)LL7=6<DPq^=99J`o=zEY-CA*u_=ov%L%CSenOVF<T~*SAOdc<&AIWA2nR z#D`~5NMks`3Qe(agm~K%ag&By<sv0nWOA;`HCV&-XBV#A<XlwY<ZOr6lH*sOuYl4` zH&6RXiyo_SHc{<}=7k_W)F>ElD&S>LIP(7jFn-feE7*06^Dr%_HL%SX=U%+KYL?!L zZ=5*LHA_Q>#_lB+fB)S6Q19ymL1Uc%)B>Zhk8v(>iD*H!h%&Ab5tgT)R1rnHL=@r@ zQLkzdwYw^!3l`5j>qO)cW_{CY#qbcN^PDz;&&J_3lyFfp5&Dznmo5l|lIuA)Ik0Fj z;5?KcH_#PcHvkI<oX4%sFRcbIl+NvagM;Rm&O4X_F)lINBRsFnsqetC5!?yjX7_S0 zsn4tI5TG0rMOdFTE`xf1G7G#~{(vfQtPRu}iv>Q+9~-yQQ%?%BgetMEP5MsswfgqC zmG@zLV_&$ou!YrJEC8z#TI%eIwJc~i={vTu?N-f`muX7_EPuJ)myL=1k`G9?X^U5k z^BwS0sq~yrwJ3{Uz^DC^+k$qO{hep-@iCTpOb_iE34X<nNvk8XaPK>}y%+3&Z!V+x z2B{#~=020$a1bMp;gOgrA9WcHJe1iJvwknW6YtLN=TT}qY3^u+H9aU?t_gxO_tEoc z43@*8O}{kFt!iqff`0H+@`kFwc=`vcpX!Pp>Rmu#trTY1bKkfB6f{3uu$d#e)KRz( zi9*XuNIQ{-ag?jd6@8~SWAs+{q>aNGUDfJ!{}>*hsJFw`5t~}D*~j0f$Hy0cb{xT* zH_TGU?u$vV-{;sv)8kOdV7yO&4b`^7&!OT&Ump75(2;uY+0I`)=O~3QDBOgL@5S#t z4rMn8g1_0`*`^@)omFRe032=^<&TRM@#c*;pNmJ)?>Z_R?>i1VzF<0&cKK@hh;Xe9 zREOE;;DCE`GS1lv-N|v|Fvf&V6Wr)k3#WsyLB&hw&UNOoLXCN>UJx78R!(Ha;GT4> zeMuafcgIu~?#AU@mTy`x>=(d(oSMu!Skq+I91fcDZ^A``@1ku{i@|7ape>avuk(G1 ziZ)$lZ}=1bt~$-%f)~_pnfg7Ve$T7lW9oOK`aOtW=g>s_Ja#w3JdSTQnY9$3`ear& zyyk7&0T-n$^)0*@lUYC3#oEV(pexn`rmaoU7l%{f<}>Q|9re3`zYm?nZ%WW-ru=pA zkNr9xmkPJ7h8^_n;n%cu4y-ZN1f4O|Xu5Tmsp@3YX2zvWHU+v)Hqn}sO(V$Cvf8Hm z>LVWPimUgoHq}IOLDNbYg#{YD8Xq(cXq+Jjicexhh;*stv~sEmyNR@^rY&%-vzgwD zx8l`a#8=Pa=PTabil4;$LS>KQAc~hWg!(Klz-x*fQ$hg_sFe0JGKYv@3|g2{5eZbB z(z19IY@l`wubda!s;f9vPJQWlJ;@TqU5t3!Rf(65jJJV`S8<@&UB$?E*BJR-{JpnE zcv+-1)?PNvYO$9=&8fW%YEJjVNh687Zi=_zC&eC|ZfodqNw-EDTl_SvHHP>WKU(o_ zE?$Or)7IMdvfj34DfV3Vp0=AXSkeQ6N5wPfxvYogdb{Sjz6?0YT;MfAx$4SIG3eLk zm^kLo@2Q+H%M_qqFwN9Py<ncH8DG{@EWp7}V2mtM61KO1xy*r+vnh*naVe*Zkl$2Q z+8rGOQ~q}Rs_CK@@Mg_bs!AaMcWT?pOa-SfU1X=K(v^Blnp8WA$VQC;mZELt_|UXU zZY#xWVFAkm^z|1mL-czK=od>vqWCyIFBXtmZIbCdSZa}&i?`vu(#=*|w|8t)Dd8|l zt?gtIWa)y6!K{gtV|;nxDkf^mzl6F1yEN+QlPt8fuO}wLv6&y3iCoqY^ia(PuBpVE zR((KeGxRlk{l*Fp4YylFgj59d-NwN44i+Cn#A-t71n{RK)Q5<-v$iS!JlYIc6ubc+ zrmYn89v31E{5Bs%a6|Cd;oUlDalt;AMFpGii?uBpP)m<rAvdzUD^l(;MFr$&jB}7$ zPr=Y;uBmYIMp%{9PAODwnh(qy!&0kyihBbGmofoL`e{>DJv6pboRykXhOyp+<+w`u zDE^tVP3wuUDE=PrE<B8J{`x6}=b)O9f|k^8Au3q;#;?5$6IE|3drVY)k1-7=sxmlH z<*z2Ho`Rdkjy&jVWV(~}vH(t&jH##?kc-aXi>e6c&p}4$EL3_?Syw_YJ@umUwa{a) zs?;df#TS_~s=|RrRK|~*P?sW+M=T$KH;?0v&@x9{dGV+Cu-$}OX{s$=lS)QXGBju( z^n)uYb?jSsX)Wv)+)?zhrp#2WL#dh^%1k#P1@IM9N|k)aVKgW+rI0e9!$VhQx*IVr zhovJF%1j@`i=OFnGfR@1QeqfQJTT;>s1>OY@vh2DSFx~AndvtmM=3L9D5cDF6JBDl zt?<E$8KV^YHu8YlOuxi9OOrDAaG6sIR@zJ%sQ~SR3srfIFKz}oF5Jwh_p0_2^@J$# zSK3VPLCry#f1KSTYBT)^0X1J8;7iY4jr*t>!Si|WnHGq93kvolLg*RCuYE@>zCXen zw0`5aI3AvKxkM;a0lzEDwzY*8uSMezm70bsrKX|fkCZgk-N0Hyv8ihMb!%%)(@X}% zdXmeLQ@VCjyQ*LWr<q8<k_b#QF@T}ol=f76OH)^GT0kO-HeZIwJCwatHKMDAQ)Y#x z;k4ET&_)fXOBunDikT)dMw@9WU_?sEsX`QmL#smzRmEkU#PNh<PhOuuYn&{i>^YPK zYW36}5m?e+Reai{dZl}10WYaDLQP3|dF;gW`?&xW{7{*eihbKgM2Sq;0O}p8c7;Ze z0Bqid$a$u9DQSS)YCO{dO1yCEP~$Z7xRk;oX6;_Z1#-->?FhaDRD~I^jl3yTqPW4w z=3jEF)+nW!wN`0_bBUVSU}1*NZR#{VE;lm_CT#e->J$7HDd9m)NN>*j)YKAr!>Ofi zT26b~+B;M#CC$?UwYVL-M>soIkNs==wu1;MY||a9&fo>Nv?fAJFy5+E#6}IwnmRsa zsPo-lkZTyc7ckeL2-RP1rjtgDmYj13W@9|I(ZjfcFLO7Rbj2zcK4eKdtwd`SNtKHR zU5cPB`m_>1#JnClLDo(>L07RX9{w>Q%D8ow*|%+ASSmE-i_>Eae5_Y?<DeB4Rt{Av z&>MjseN{Q81nq$s9W0&+4)s;NOHM4Y-++lFH(1ut-PJ1HigD)TQToKvQ*T+sQ*YoX z3ZUDY7I6>YKEQ{7ci^UN1H@1@9<vJLw7Hg?SWWi>r&5e*6%(%Su=j5uZN2mhi_ypT zvE6ES3g}FSx^!EkxU};n-f?NamUzUaUBC^{rx1DV!WLdVc8o8%+4*G#JM8G`3FkL> zwVSzXf;$&A1fspQbJ-uv8y{4k^F29nj-8ljaQv)r&^Gk(qNfY$9+2Ml{(;gOsH0+Q z8SsJCH`3}Ic?~S=K3*7ZmNapWuEb&@UZH?U>7_ET&}O9koFN*9&h{1F;jhZPOLJ#S z-H&^PALsfRkf=|u)|+u5%o|fqA38j})zz6DITh9n!FV=`_X?{UhC!Qtxv;)ZABxB( zdE0v7%E}Q~xmOoq;=9>Z_xeJQ*TmDf+Sizz3IvaFTbs3|id)+QsVkf<3hP5fwG&Pv zYq0hDDDd5lTZ!j;Bawznk%*of7(~~kq=RAg3qbv*4IveAh=H3bc<|v^T0Q4C4wf+7 zpUFXfB5EAitzg8^bHSV8rNvYf#LBDZHmZ~48RFN0E-toncq*G(Y72d-$^K7RUx>h^ zq~q-iu=%17Fy!&eaZu%k9r?=cmaAD&3-fd(9=vxMCq<kc5r=*LF{mIYnuLps6y1!| zdJ8^Ch<%Tx#E!!SxXTssn~3~w72rEu#_WcnbbyBE&MRJE=E+(frG>WB*k2-Ta|ai9 zMj2NZR^M_T!eIyfN!0#{MLvoSOaf__S34Rm+@)yRmD6;O1sA1x%RQD_b*W1b*Hj}= z$yYnSuLYernj{>+^&PmmL(i{06dc^Qjz))E^>p38!lJ}XY?6*l1e;@dgmHI@>FkbJ z6di1YK!99qqW(H}r?a;84*dX7iYeC(5aP=pGk*g4W8qH>f9~Q>R#9Odq90;Ah|Sw~ zICf$4gw<5yfq81Ux)nwG4uQUeuT9n#j$J*z-1&pM)w{4+QKV-S)V7`UuzD?S7Ba;4 z+xW4&9Y-#HY2WP|fD3C!Iu7F)AKctRqHMqIEMXYL<T=z<c4zTuvJ$#MJEP86%gb#H zC6$%4VYqh17q=uf#I2(BwRtZ0LO+!0d$bP^@D-EG7<kNT<jllgZtaL=BfMdkId&@h zaf-+-7N2Ue%v6A`g}~%p<JU2B!l{#4y)oftLiF|GaaH}@*xrpDQcizFpiN;pn=vlV zbfIo`(cX(t?Sn4QHajmt^-o%xNri#VRd}Pn0)57-crFlIj6*4$!}HSgX{i~r{;)Uv z1me9Y+9x(Hehl`fMmLU)E1c+~X5Y#osR-B@SJjycfCMJlyn{ZlZYy*vd0m^2x0l^* zDu{s#PO0SQ(7bHAcREax@-J-W1}Vkk8In8HIrZf-`TYQUbni6Q>p;vs;;N$sP!9`b z*E3lnaJa+~j=NUX<)wbkiOLQ-SeirJZ^j&yAH8aGbC@Ya4wl^P_$Xi>PM^4sEvW|$ z*zcJh*-;cG+>FW|YBH(Ow!|MjXv|>!{<Ojm;_B=0!kit}&j(m<<*|ciO2sc6K6C5| zsKqcl%iJ#>VLX-JC8dg}Sm@)!iHHL@zA&tBZ5-6y>1na|6}F3GENPxG&e?VlUy4#{ zE64nicUm3ioCToGQ5(rL3AhsD+=o$@I&9<cyn|)!M;x2MhAkeWRPjR+k$+>*MBC2e zjx9fDU91o3Gf*$$o*Y(qEHiPqff5x|&~a;W+JHFcPtiyh+v70@H9F{oH5NxM`p$M& z`svEnkfNYk)9`Dn>+Fr}S*vXJ*ygOEPEK48W$l5kKsV=28{kG=!OqUlu#Yo0Ug<Xm z?!%pnkhq2i+cI9=-q%)!!jD=Oc;1rc>Fm7-l&)ori0o)#U|+?4TO&B#qMWo;t=kI& z9ZKCXkbgCRiiye(p<XX_MnFP91n#C;`a4MM+ryOqE6k#vZ$g<v4^RkowNxjfRAiwG zf_q!B;NjNe0x6iC<~|<UDaxG()&mWX-7(G*6jYrjcfx^guj+2`&h*8)G?)s$MH(or zJ>Dzw9E=HV6grRH7r(gWJ!r+-7mK@~dqUQbQzm=#dFi|dv(H*V#r@C2kP^6HMR%p# z`44;{>&AgP+&g!av<&wgT-X5U_w}-!Q?*90$vzzXPxHhmjNEXZf;9>aw_)@$GNw2H zZ-~|gPRw_|c%o>qJ5+xyEkKL|;DR{r#%oNPryj>DEe=irCNfp1+Vpv?uwmg$PqL@G z%IxAV-~#2AW5zg}BqI{w`}I%*UmSf1U_f=O<P6G~(r?lq^kAMFhpW#o8QnO4lv_)5 z!+4(<ZVPsq`EHA=4{=5aGU9>h{~D*jJ=G*Q&eT1Ml+lIOs{s2MKj;F&CD(4$Z{m$x zE1`hK`RX_5FNHgm(zL?SxXe#l$MG6n7U75C=GfQveZ;{_ctd#fd%kZ#=`FvR7VkkW z=6a)Iy7w)-sjI-^pi{R=3~Dv>C&t3Sj4|@DsdFpVGW2^fU*NKaP$%7{afX1YG=WI7 zoy7r}d3AF=gU)4pI(B2pX%DIqND<KZP-PlX>-`8*pW~H#7{&d7gQ{oB=;aV_;ML3J zAl*P=6j12#rMhp?IT-2M`_!`4b9Pe5VDFc(e<V@pOST1F&Yd|A$>vN4(Z~(88u9qo zQW|#%oASfJNG9_lI_cb^+6N*^O<xy}40)t5ytM5usICNhw%eQ^V6{TiK<GS-SL5hT zp%-v%Yda6kN~V13-bYf<xaef0-K!);!GVC#Py)jKIG1?Ua%@p!t;bwfTMYI1Xh{ez zIE^=Lnd=E9wc3p<hsqXS78Z;gV_<^C)<G}@)cv)m2}OUm(u4x10eO+0d5*e8!@Bz~ zX_)u*!o2t07B?*EP}O!(-uvz)&b&m=+>-j0E_to<3aI$iR$HkFow%FKXeV|EsLMps zmHlqye-r1{$wpP?yc4gu3lARZPrw3MA(j#*?v8itQT-ZI!A^my;gJ1Q?#>@-Ta$4M z@?)?-=Ooh$FdUtm%rR#COk(GzHedv-a^qo@n*giK6bpVbV(>HTF8nOWg2PnU<z~Vz zcQ)*DbF+%J<RQ+Y?fi|ht;GqmNL(rXgD1K~O<mK=tz9(Bw<y;)%61kPa$Ef|Zowsc z^&K}CHZ7XvS(NJ;iQ83hEt`k64$s?1434y296Kpt;_f#vp&|kf2D~5Z*kyRQd2v(a zVW+c76hmz1#ue9tY&r9GvjM<K*qfb;@H*~7t<`83aDz#j+cX@kvfv2s+5}Y$@OIa1 zLyxmMm4@+8Vg-lG?t(9lY9LxD488nN?a3y?P!=#qad(bGP<=QMYag%?X<UJh;UsrV zIr4)-tgW14bsrbPmh)gwv^P%mH0iIZW$V{m8Pyw4{rd4G%UFdN*N-=I?ga|^)^}X1 zt=3_S2cVFv3&@{Sj%~oAl2e%0Xv$lLdHr}1Y^q&9&ijYa-;Yak$4%tp>+P<%VY##O z#Yj-OL%V}~je4)RgZ$Bxpb&D0JIEvWT6qV#ok?hSkh|-5kOzE#OUMhPaS3^+gNntd zxJriWw>z^5z!}3Ezl6L=9M6))I!_$0tU++&4$_^7MP$E{mOP(Tj=Igqfm?B5HL=|J z$^j$YzPOFN9&aPpmal6&cDKVUgQ&cY9OG%Muc|W(xQ>AJ$M7f6!_0C^b06b;EgZ;d znn$gz;0E>o=kiq4V2CG<2l{A=4;M~iC8JL8xh|0^{T^{x3a<B_HJWwKe4ni$uim-E zOuY^5>z-ax+u8xzLE7SEKU8D%`##&N-#4?}-M{O%7jL`qwx{1oTpxftDi8H|uir^) z9jsqUneBe@3&+m!>~g8|VjeMR9@CH&mT4`1vp_bf=5Z~BZ?_?WR-8h+f}`r%{Q{M% zxLkzg(rvwc`1P^X!MEqdQ&>ZdyLd`p#>JAXhqj=5%H!~OILUTPA^ZP*{$Jog85Br) z)p8Slfc5|jU?d;~Fb}X2unF)!;3S|Na1-vNX%FZPhyY9iWC4Dv>n4r?*5Q34;4Q!> zfHQzA0N>gO2j~YF1F!-X12zJ701g6<0e%2n05pI`tM-6EK!3n+z@30;fLVY%z=MEw zfHwg90Y?Bo0LlP$>$r(FfKGsZfC#`?KsI10;3>dsfR6!R1Ihq50e>?f5HJuh9B>!F z3djen2D}2;5BLqhXDMi_{_Jdt1Ngxf@y$x;GkFiY)Mi^Myqx^hBC>C-{H}1&U*4Gh z$(?*f3nHTV!f|(r5Tz*4Lt2H1Dfr8Q)o3wFM2Ie;kIQ>^(OV1?;jp3ma1kj&#Rw6m zY=(#-qMw+7zkUeM7=%dD|2hjZ($fCS%8oX3^*`bfExIZDZpw~fV_?T8L^s1kGB8U< z{FCvUt=xu-OfjpP-3a)y!rt%|2lp)4xQ4_)PfP{mz@ASO-qVq?@ty(Sd_oX1TcpB` zI40tK3iXhJFUg2M8=+`tgi90|E;bsz0$d`F0(>G~7?>)27&mb+($>rjd@~)!sHJVB zYotkkOo#C#B0d|^Ptrrs53#NM9tCXaBge%q9_c3`hGZApQSjyZ9Sxi_T*Ab`z3Mm9 zHqsN26s7~!?J915Gd|+Zc!(>*^FTts88iCjDB(!L)7c!2$IO?xctmt`x1^+Qc)=5c z><<BiB~MA7F*#Xf`0&hG74IXaSTkuImz-raEJJKlZ8<<J%9gI;h_Yp<j10-jPE~oB zm_0@1U-IN^TVl56Cox04A{~MF1>$9#0&y`OK!%7;oGTCq%xn>nJXu5~W{9{%t1UYT z4tOH6Q`Ot3X}0Vf-7Y>kDI;0`7-iGmqBAp;Yn)9t6Riv@5Kh3qfIk600`6icO4Ue6 zPdG|k4{^KbigGp#e=5E7oQUk?WD${`6PIiqlbDWhcpvQY9+IA(IYoKKkDI%PXDzSV z-gWBM^Qqs!<lFG3Mva@?+|;jG^IKZ9ytS3Nb(^;S?b>(fcw47{&Rx283+#S-kDk4H z-_fUUzo7mD1_oO~28D)&M+_bk88viR^zaceu_NO~jUE#}cHEugCrq4_a985wDM`sG zQ>Ue-O;4YZk(o6!JI899HG9t7yYHDde?hJY&CCv;lWL90&YY6W+@As2n*!O$hLj|O zvLuu+<_}9$1|%yLK9W&Gu$*Tre`ZBWeZlo=%GWTIr#Sq%`q5nDP%8}=gKKbsEFn}h zN)~-w9a4bby+t6n-9s?0F7OiqY_z(Ab%+^|iC@+n#4j2cL;@GHq9#e%r6`PND8JJ{ zNe<o;@yigbyI9Y#4rIAZ1+`Q0m7&UVs;bLe<Dz>i(oBVWI)3lg{jpTlRi#dgpZ=2I zK1I2+Br{DjQez!shD!#1=K^=8O1CWhF-9#!DqJ#<4`xt9Dz#W=z?L<nS^1m}{59OI zDD9-4xtD_&)0Ll0kper$$GkKsV_j9rr!I<5GmtjxRMtag(GfNO6ntfi+whfw_%iTK znu!x_C;{XrDY}|d845>Aj#lrJK1!Br$S{QyYgXdbRpl<_$jI;8EAl%7VM%c^{E=Hz zL8}=lWFahDAI7T1o(@x^mbQ#nbD0632KI)$8tHVeNT+7GVk}kjn{gZb4h6oW@XdT7 z?==^V!{in5>-ry&i|TX)R?uPKWbmyf3X-bv`*!pxjPk|YPE@5rqlcxdrZ~(><|wxY zE|vLrySSqwJ_C;%%fH!3tL7B1&O_JqdjEy=Sdv&q|4MqjD$>h>Olo;Q3vp#5PWD04 z!L_SPj!_mXIi|_s?V@Kzd^gUo1Ypiy!yKe*MVTdsj4w)}k&Bh78Re_H=v$FqP5GUP zTxEV~H6P1!rm7uSOD3aEWG$7fVqhNd(dg)2O^%2SV`4p^)h(>2C^I$H^{(+$$`A3o zI-VKeGHW?fK27mIQPo{q9Web5<Nqu2QZ*&^>BwV^y9WK0<&fNGtzboc%6fDf{IV5b zFWBI%Rx^_`MjmPL1iIwUjmraL)nt%z!S<Rhw<~^uF8Oog@v=wFzPS-&P6f6`z6YW= z#B|s`ryyT46>nH;u&v9&H{V%{vvp!ir*Vd@hgQ35VJKadyr4XAOce7Iba=un`_ZDd zNvwv+UdLFNoG2798^Tz9#v*XkM2v;mi1sl3U@R}ewY4xUFrj8i9Q?r|Zh?6hOe(AJ zg?TIOi!GuROmCQGn5&%@(HiE)?<|mG!~>I^ODoK~VUC4a4l@QOhiri`qgB~p`^Ykr zqG%oiJJPMy3ZWtZe`b^zN;V}}>sbxM8%Hpe<CnUMN`V%He>jj0zA@&h$`{*T*3?>P z#x-4Wb2fel!Z-7#Y6{^9r}f=hBj&mo&$-6dPtn{Fp;@xhA+vlsX4ulx@ruo_UYG#~ zzdgK!m%FcLczAd%KD`1F4?UXu#Eh-&E$#>mjE}+QJF}TtCcN*Ob{8HY=48#m;|(9U zSjyWQhByBB`QHZ|Fkki85%q@lceUHqHbamz*Za#CSN~P@zfe^ExrrP5bB$q<sQhzB zxxJA;BfR;)GH_M?v&HxymH@Yf6@P9w_!v1zbCFx+pS#<Q{Tbn}mgqlg^G79sDK*BQ zks`k;-+iIx_s=}l{ofe1mA-sM<-7LghT0VevKB6~=NH_2-{Qh0j-^G*?q9y*9}hhE z&_5qu`N*S>J-+IRCs(g|YVEr9Pd~Ha+2@{r;l-E!wejUwUfr~L%huOkf8))!w!OW5 z$Ie~5-+6b>-hJ=A|H1wbKRR&m(8q^A`Si2Tk9=|T%VS?1KXLNZ*WaA}_Pg($#Xpps z`SGW-r9c02?)<M8E|y*T?Q;3=xLWJ)PE1^T;^BrSCjPhS|KCpkZ}b0;CWfx<t|o^5 zx9P8iyPxXmtwBq?d+P7l^jPs;gm<Igu*~KCewTObVXN@7!sY!RF7FSxyz_2jBhJk( z?;c3M4gm299{?uw^f|Nm)QqIe*>ToHYbxdkVLv)2IeWz9wB#w)$c&WC>>0`-UJElU zF~=G*#hN-RIVLm9mZjp+zO`sXG-lxvrzQ`|oD+|E{5Un!SbdHWQ3<cSynFK&=Ak3z zac|zei}D)Rs)e3dK|ui+7Z{iqleZYXs*WA{#Kh;JpM}m?Ow3{gGk45eoQF^X-LYxY zrg?kUo|Ba|J1eV7Ka48}!vS1p@Q2@sL~CNYIXOE!Guxb+VNOr9WlWitoZZjdE=NuJ zWuw2!Cn7O5Jvqs2%`|6bC1;qE=Oj<DSraFxbE0>224Cow0)CkjGt7xu@RS7qocRSq zy1MwuPEJfRr(|c&fNvFCv~A6GhY(;i1UwlF6Pve~D4wXy$-t|E)#jPD<m|br8B@(E z3ZbjqbCRuA7iW=UO#)d-wygBjDJrv!fQTDznKo<9j&K80YIduncM6EHCY!Ug8CJ6` zhe>y6m!88jCoVjjnrsEjQmy7GnMuj!%oHO8`~4jEl8XYPd(LoX!<>w9LIzB2w5J^L z6Fw&kf~Vzz#%aViV@4u)4sJ7PklLXu@}>jda;7CuPK0H8YDO~hGaWO)HN-J{TB<cU zCo6GEvN<uunw)L!(9M>U-EDGeMz`dQSsjdkl{BlAEAyWz!DDK6X2y)<46EV4YFf$J zGg33aeqaNZLs+`Zv}J;E$X6Fpx)#!-T!L%iW~W-GG3#=yiP<XFKNFoxz9?FBKGnb* zutVXkl?_*ZR>_N`WR<P1?z$+99u?80PZhr^#SU#dm=ksEDGjb6Ys#Yztvi5KSX!8^ z<O`vzWp53*SIwa+DO@c_*;8%Iyc~1K<XI@)sVU~<8Cll3w_QJ-$q*U6;3sn3gGIp* zND7^KM)HhIEcdh#?J(BNfoay?%r)3yor*&97atzJj*-x|kMJYo!s6W9X0<xG`&9UI z?Kah0>Gks(9_$S5H-Ytc&V(@##<>$v$Fm~OnUIq@BP%^Q!KnKtB&Ft9Cs=#j-Zd*p zRet7Pm{+(1Yqj^*j2!l$acV$(qMOEdKy!-<V0>41AM1a8_l51Q@BU)P>$|^t+x6Ys z2VCF1R_Chj`(5ap&;|E}0Qea6VONmigYmuO_NwmH>7N)>)!j9I#@h{R?R<>*s)v7d zkcG|_?nkPne>~Ju;r64;dv$-S!z=y0;PSqsT6`f<Rnx0ZuTN}M_v-ZgbEM`Dl*MGc zUyH70qpHSJJ)P#0ukUW3d42Z>W>s~sj^}szRoz|r_1L`@@e+WKfxoN!$%icBG{Dup zIv+oLxT<^ge2sdfs(W?%$F9G=d-tcSx>u(!Yg1MC>gjjhTh)DEH97cspXM&`biw-z z9&UV9&jRinIf=RgdvJ_rCG5gZ8DCY+|L)cK_wChb=H|NGeV-fp>!DizXc$_fc+t`` zE}0$Dm_+Necrg=SuDy8lG_{_+*dRhxzs?v0U<je&vSnwZk<@L)CC~W8RBJ?Lb{rbz z^khBkRQSwD&PG!hnwgQ4nVuYK%}x(Tql*0zH;a&*oYbiqdJLm7E0Yu_m;%ucMGw(P zLNs=VZFFXmEj>8`o#o+)GeCw|?-9#hu*(RfGNP#-(YADJ>Y%yS<WZUNsY%J9(-O1A zLpntj{z9-zh;heRlZK%G$bPsxzd42p=U@PmP5!tLq4~=eP7$W}rjzxcBSmO>W{&YS zG<@Xn@L^~@lhU!dAlxm^nvMTR;2k$)SbRuKq;fdmJ|sCYOKqnRAE<Y2>%>nYJOkaX z(CkzzI_&9jXrMXt5`8^}B`3~GzREsTqaqu5FlufVxpQx|d=C+aRs2<R8+qz!^eZd* zeb{q!#x%u`r0_XYu*C&wgYiHJTqi%S?d%bm6P7&LHg#%pc1(714m124_s9&8k(i!( zcXh-=GLqu5QZqs`ZSeO4Xl4&GCNq_^i}$(v#^u}3bEGwWbOt(qN#a9Aizc7gxuIx{ zp(Kd2NDZOU51XEx6q$jc3A=RIWaes*hz<K`3>y*}Bg7r#;fU~PzSjjE*x8brq~s8z zRq?LpsPr6tU&~&;!?U*cWgox56zyvdzf^|$F+NRdH3>nk<dAzV()F&wTq{wdrg2Od znFMKJNJ@W5QWBVm5lg#T@el<i{UVcbXfbMx6XzHUO9t~^OwnWkLjqeCSrRV}fs^UU zD2vs^=@rko^knQd>f$jhG&(U0@(K9?mODH~0ux3kL<&>mtC1}t(T(JVR}OZxa5?ef zDDkMtK{Tr51><4~M%imv%P5+oGAqifct$JNG0E9#yqhrvbqM4G67c|I8I?L^x=!~_ z7w+km1=u%N(LXl_8?#2GBApz?8N7-6_3}@PcoFO|EHg1_SnA|#Y{mlBA1j#}nXF~< zqbhE_@`6OX;PQ=31!v;jBGPR+(-_$xTS^Lg)I!`xZn@MZo{%FQv&`%WjFN5HC}zp3 zTqI#<(u}Oc?Boi*$1}7G|HdR{r*dc!FXA+pq!B4h4)Xz|QID842zuRG=|&k7!e5gX zz19M0|6e{kdPBtU(9~v}bvF3wri;O~S2vgM>aTPs{P+1U2X2%Dl&9g}S>AlP+4eAo z;rGn|LzXy3=es9>YxlJP^#L5Ca~`%ffb+1NtEEXhnw*fN8|RJ<H^$4bG)(};OEIS% z_X}{Z0D<<c0kp?(UVVq?-=X?9DmxWsq;4Olo2*9||2P2CMz==AGXtg>fJ#X1F+e9l z;YvE_KMz2h7wYCBn54xHpnE=m_+ai@t;9c}f3JZ_eAfY(-ZKFD+X^5}9|7q8Ie_kd zU<&y|AYcBokMA`fEnV|9pZ_dg|5LGFd+|%d;M$8X|5F(L=hL~S2<R=$HATSupU3Tg zFoplyMWHeJ2kxHU>rf%zwP^05);jB+KB2v=S+AK3pFGJeP{OhxPnjFwf9KkxYt5ST zRlf_bXjT^8+<b%nLv;UJ;Qzo=r=MyrzJ1F1)c9-1zhI3D5sL;S_UNReW|43-?da`S z`#*f-_{mE`bYGxh#(Aqy`0DemMf3y&0y+aa0{j7HfFHmY;0-80Z4spaC*T<12;dXI zLBM{%KEOMG9e}q0uK_jzHUeG%tOKkBEC(zG(0?9a4j>DV1egGb0fYf8fc}6$Kns8` zpbi>KH=QzXd<#I?H^2+v1e^pM0qg_32G{_25ReDR0!#pm0t^F$0r~@a0y+cy0WAQH z0X_gvK>63Ws~T_wuph7kK>wRyZUC$V<O8gLy8y!gVSxUCjsO8Ta|$LNH}(7P|M71Y zQYF&A`%OHn<LZs`S;n*SXUN6{i&%XTG$QTg&2eT}e;z-F{egJ$*x>(-$4K8Wji`)o z!@QRLwcP)#e<L2lG{XPa{QDgEqdiFO)gBN1F;WgJg&YDXkB>s`%(Wh9X1LMps)K;+ zwg~uR$kiWD_&3A<wSZ-T^1%3A<-&3pb=D04f~kjnSJ%f_N2stHTFa~A{l71NnFDAt z@OY>-(T*67G{6_eDtR1pErtn0J(|DTDo<C#p84|{Ob?g`Vba|RljAga%46pE!K@84 z5GD-uXz{qI-3&u&u&2!2Rf9bP&v6kbBOcl>zJ~qEYuInNhW%^Tu-|tL`y<z|ch+Ff zwz&-U-Xq<F6U;lU5g<xOxrvUjH@^MGxQPuIpc&sgCgI#Om}-1?OoDs6%I|}P_(qS~ zaG&!i{3CAT`{Wb&29J#IAy48gwM%*(;bsO{0B%A@3hy;NUAuM_g9i^5@$vB@H8oY( zY&MZck9m3c&l4+Gt`yHa^Ne`?_1DFY9XrJ5pMNf{T)DzFPx(@w@lnbzA94TwJRf1& zJA3v4^?5*^Ezk2QpFMltJbE}Q_m>}#`!B+IFTTC;aTa0mJ$p94od=+9L4Ctk3UB<J zmE|eQefGRk?=uK2_vqiV4|ta`d`b%9=aWnS`wyg~96<W&Tg9J}k`8<L$z}ZIaOVR* z%0I*NNxz8ia-@G?kNQR;jQ<4FSI<SH5A6{LxTr`w;#Yp)(g}QBpa+HjqVgsC%lBVk z9Q?jAazZ3Ll&2$peAjyGy~ejazW)G7NFjf`kG#0B5gCA|jNiW(+}?25{sZu_6y6d4 zvyXP~qj^x@Wgi|`*XD)&$}im!?o3F3S%%<h4gmOnw06|~vho9YJLnGn$lphAFDqBh z^bh_PKVBx4v*JIaaB9x<uhd-}(VSKM3O7d1_!jHW4)rO@TkXg_>5&(lCqye3@W8tp zK#9gROuEybYdFSJ6Xe2P<_R}|2cR~<1ZX8G=e__l;E&|IXV0EE?~D_qadG1AyYE)G z88W_n`Ev2xbI*xQn>HyK|Ln8R#JAsmTOsFJoNn2OI&|aK+LZKrvhI;vQnriS?Ps^A zOwSa#$fA_(P{OypBmt5zJ@=<y6Sm+b_la+zeeQC~{P(^cJ$m%^lwm!ehnX-vYUT(j zHz&vig&nq!ADtj_<=X9=M>D?Hp(>^n-}1+c7dHwe#rHtnbE{U;w{|NjJaho<U|r2% z_@RG-N#hfFWKn!VMRc8~UAuN7ARqwy4Fko10Ru!x2+r?DMk?OL#>NV$?1Cn#abn`c ziDE%ggqS*Ysz^&q6EkMa5ZT!{7mE60{`~o3jV)L_fA;|K>VhC)pBgTfP7f6iW`>Bz zvMu7xh5f{fd6DALg_FhBm04oX{X@mUwbMn%x25R3ON#D$qzHaTieB$a(f=bUCVVJG z=qFMPJt{@)2`O>_qraA7{P$8!IVr{DGg2&ExKI=p7K#-sR)~imepo#6$RpzM#~&A~ zSFaZ9*RNOkyK&=2v3c`mRhPZ>)?4E6?u}y6&r)nImEzrZ-xcq@_n!Fh!w<!wLx;pC zpL`;Y9z80)`syoj_S+-k@GnxFI(16PMR9SlIDhsB@y#VEN=r+{#fuk}tdOnl-7voy zgE>tIjrVfQ18#)yps+V6g`CQp!~oe{jF+)uuAC`W$`xX>d>Q+P4jJ{SXpHb}V$i;3 z2{B-~5W_ZN{t@A)mZGhc4aE|Ke;naoLiimB|1rX!b_w4e;Vm&j+?j>5Ov{B>wo!;@ z5q?*x5Qh-{2*Mvn_-_!t7~#(%`~{cr-P&VMW(Z_`Jod$66>;M-jLDzHzJ}c>gdaB) z@<?|fzls&|^h_atSRrKT%R*i_RDplD#t7dA;R6wVAi_r@JmM-%MfkZ5g<R5I$W^gI z{%fX?J69mimxcWHP-S>@K4Lr(-V5O|X}S^PsspHhO3{gt=9`2Z*j>m8u|nQGQ^<!` z2)X5DAwM}(8D2ENp3<i1@3h9g-T)Na-r@ixzZ7S!Wy3p#?4BiL?7c$Hd|b#CuL$|_ zJ|PdCa0zcl_}&OV4B;mu{2YW|hVbhU{#As38{zjNJknfo4B@{;_|l5-ow0j!C}K!O z4EG_1^@!me#Bd5Rls1&&m+n%WkCo!WOerp|kmAzIQd~X+1^ZI9r{Wfb?}G5b2tN|x zry%?+gkOyCk2I9x>F!c&ij`v5OeqemkmA_OQj{F34DXHb<UkXIzXjo2BYb;=?~L#R z8%i;@yA(5HrC2%>ajlSI`^!=sJyaRKYSoaSJ+79ap@TvOg@h@qVVyd*^Ka9p{oo1@ zA%mhKBg4X?LW6@t!V<c4?9ic||KP!G6Lb$@k#NR;BwoV85&~|chrxr*x_eY~Xn0gG zq7M%Z2_6)Z(3u|EwQJK_caMy=ghYjehJ_+LG3(knAYh=5BfUgLM;TAVEq+ZCy21lv z@Nd)F+!jbiGXAKj$l$1imW`VE!5tnt>K@uBAbfBLBM6O3xTR5}W}3Ug(Z7uuNJdt~ zpU|Xnqeepqs0acSm960p{KFVNBns}08?_v&<2I}lQ9$^F;E?FyQBmPh3C$TnGry)y zZ}#!=X)%mA(wz!AqLE5M^C}(^$OgKHhDS$6MMZ~4x2oa+?j1U*_y<LYMTJL)MMvD) zyosI!Qb@S1W0zr|pYeyPBn+-4^!Eb_`~v?}{N011!Q$xfsAxrm!qMPA@J|TqZXpU$ z(a{ObBO)3#Y6K!G+!K0xC0M$JBZ=W~zcnI4QQ4xxJ=9do)TcpUcvM(4xE#?+QQ0y= z7mwh6AtASWm}&(ECqySiM}|jhSfUEip2*OigF?G`y44-7JCIkAVW_Tj_k_OPeCv3* zxiuUD42fcNR4@do(mmvkUV%O8czE9w3CGYukma5|LqjXw6A}i6j0kE_yH;<c5SqZ) zBf~1wPY9*ljR>mmUfV+V&|rvblo1^KBYz-ZmU;~vj7SKL4i18>RXD@lc!u~k>>C{d zK1RAYlmB7L2kh_Y5gLS|;_9s8NB%~IK@cOud-bd4>=HjRIx?hR)zBy(RiEf8k)wW< zJ95iRdBG>qx!3{7)8Oy)=W-E8b&xgn<?=*uwf@}o`zc0$Zsf?3sz0(Id2mJF<C!@F z#p2X(u`)YUY+4j9Ha@yQ+_4XR3e<B$K9^z)`VQ<f%z^pOfBsWE_Sj=$)v8ru&6+i0 z-MV$Eukh-tud4pw8*jWJ*jM;;$1~zF^fxx5ukg-0?}(2+`bhN+PJewueEs#;;`Hg$ zqNJomoH=tw{POcz)i?O{*I&i&zyB^)T$JKv^c4<WcByB(wMIjC2O2t*%jHwh(9K0d zcRw1sr$s}#NpzQQi&(i&%#?@43VBStEWbtjUD?ivZfFo={16_E?efkD-y7jA2p@&; z;}L!)!rzDRs}TMbgntj=PgJxs|Lv!MegEyJ{9oBm;W>Xk&6_tzArhjQngwm{*RET) zZk=dvZr<FldFxKCd>b^l75(96Z92AV*P&gvhQ6lT>f^h4>$V*_z;8p}R^0-+1&9`H zI(6*UvTnDA@X(-s{aahKZr8C}y}BK5)h*2Cj-9%Bd;4@mnA>h@P`|lf(@x#$d3)Eb zQ>&KGZ6;H5Pp{^kTGsQfON(y4t(w$!tK9~EyLD?>rxxSC+0VTZzUsBDTc=I{#sRI{ z-Qv*#t_ac+-$*~8MdJ=_1G;q!=m7kYey4x{|A2tj0gApBc+7ZOw^pAb*93h5wc!zc zWd&|9YkFvJ_@RG<6RiYJ9%Fm~xC`JW%=rCVk2^x6$F8<<px3U<S}>XN|HN}G>aUkJ z@vR4F(yCRf)-VbFfcACj)WHY{$5a%j(1jK_N~~?eFgT9Sf6GJu)CXX6b3+e#>kFXx zo1c90$#}FoZ=OAS_Pd{c`ssVLJzxL$<B#9MJaPW~`Lh_8o<4T$*votO?sZ_@A)tT% z{*Zj;zS?@jc(^5neE2i`V_vgizNvlt_HAL3SDaqHk;iZR`0>HL@xb#fm`A)H<7l~k z`*!*L_uosjrxNonoS>2?PMnY!e@nW928l8FS5Bw17_^@H_~VbC*tv6O?w~<~dLSO= z6V-e)1vCT@7v^hS9r#Wj(~VniaO_kx#au;?va+(@@Q#M_hVgF(ejh*??8!LpxZ{rY z#1D8W{NI27eTg|z3H;=1uf3-5#vGFT?z`{g!Gi}S<`k4ahCv^J_NNi%$(LV#dH&X| zTj!(O7jC!PM`UGXg)LjQEC&5*;&vM#plQ>lJutU%=k2%OPTu*2g@tuwym<dp_@6s> zPNFZfqHWu@y}-j|Km726#GGygpAQ^3AiwzH3xy~0N8!%AIeGG={PN2$)i-G}0DT_y z4w*au^Upt*LGCUiPUmmG{U(3;<(G4xe){R_-+c4U38Zz2VL;~tC~v)h!!m~bv-qPw zC6QJI5Pt*6R|A+Q1`vPpil*_-Z-PMwP2yt!aFzxj&!qu|onihJ{CDr(y%hP_1~QRP zT6XQ)rD&jhV7^H*4=~T9<b^o0OrQ)a^YG!rlEAXT{GiG5!Lq|JAAInEqJepc@-LYW zn5*X$ZpDM|%djt}JIXLOP26btZFb?p1&L-z$$y_decDrw3Csh`o5?rdd{ZLNCHl;& z3^NayCzw}LK-~B3+b3C8jvP6n-bn-N0LmN73G;}!ZTU&c<fFJ=;3Fw}z9(h3cX`j7 zlwEh={>b;GeC}H*f4y+wFv<$c|BXBf|F_?MdxgKhe=qdmm!ZCt$PYyW>m23*`AT}2 z7sQ?K%>U!Zk1OCic}{*4U&;b$A>QOaW%Q{tQigpdrR8H>NrEZ(JFsTZV;^XEN6Jp1 zq5U=~+q@y=vSU~qC@+8fMv#Xeg+J<gX#nvzz{m^3{43>z<$&@Me_YDJINTNbDfmws zkO#d#kn(oWknuUzJ8<V-$|2m6`L+_P(i_De^Q4sJr9FD|XaiZuCmqNKMUO!TP4bd* zME=)A2l-B(Gmj`Ylz-N{7_%vaMgaezUurZA!XdALz_lM}z<jdI0$s#E^{|xwZ)wHi zM)60RA&vT<@{jgN5{&$yN&F2tr~ETNC|8sXgBF%?${FRJWy3I8F8IWql5#j`h=Tk_ zfZwEH01m_T#YGRKArNH&^W?JQcIBP*=#4zhh(GG$6`14ig?w1Xa>lx)CORnZu6bg} z6;1M=?rawrmi3J5Gv+kPC~5dg%1F=<4jMN8=<4H|??1!k(Q6RX?9!!6675VCAPoi> zbkvk51}(01T)uo+9(sM1Tt6>LJ~}g4{xj2}5WDj`DMx=JW$Z~Qqe;UTdU=M-^f$^g z>m-zC)=BMA4p^SMK%Q8puV9_61{xIp$nT|?yJ&-YJ)g9&KBQ^TK$CJ$xvox!Azzer z%F>Dbo8&XI`^&Yq0rH8Qfr<taFtHeV{dF2*PDnWnI1K>}73G;U=;gU9>m<~v?NBGR z1`VxV)9O}4v#=Ts3ja23+Emp4Xye(=UzHy$zibbT{9t+Dw^2@rKk7ZX<KZOv{M`QX z>DdG1Q=nlLXyB8G`f~zk7>hc76mI_@4Muq;4Murpoz#6V_>LPPZX*rgzZp99N1&d< z^HELsqrO-2kFvIm{UMe)gARih<^kIS*E}(3p-KE%Pi|fqB44^ENInM|)`NyMRt^80 zvr^tw0vepSiV8HaJhM)ULY-ukXVPGlXVPGlXVys_-&FWttd2j+8QT~1vnqfz7*L%K zqpY~n!FSTYXKQX>`O3V0@};|j<g;@?!>j@F*U}&4=P1skAptaCjZMb8lxNmSEYBe* z3#^m+piW}@Y}82|w&Pj{4gc!(QZwR@{{7Nky?V7lA0?l3uwJA|nIRqQ^Ux$Mv}0Rq z^vmeR_LhAHK5yjpm0K3{l`n&a7eT`Y(D2qHnezNu2+s{X#h`Nr@}v*jXV75uF*>}h z1+LD2))$8S_v_cMJ@di<mRI6U+=#nD3+sN?_Z-)--eg<FwvEr*i~7jdLBr++{p7}Z zLGlIAP`x}qggR-(j1akW`XISDHB{QChRWQeFzK+}DUW}CP?84MK87mKsFV2Agg@$g zCI7%@8F43GG>H@OW_ci=jXYr;@7h0Re~2_v{&z1PD7S%z*FeLj`Je%1f#sPruspL) zdIa?<X;@Ag(gw-<rh$f(Fu5QpT+u*0*~eh}Z1gdDp?$-1mHe~LU>nAM1YyI54f6Tt zpO@^H8errH&FhsD%*)DyPbA8n_B-TT3qb?Q!mFU+UwV0FowUX_P_D`zC|70$%Lg+o z^8WM?=>QG)f`&z)VLoW!Q@xKd31tJ%RrL??hb$=hhg|2AmV58LSHAGV3yL0t2AbER zgEUdL7}j~{Rk<tw4!Hv~ya^gqc?J!vlZ^7b8g<g+*}?MREQ@>qG%N!ROF%;b<Y-}X zm_n3wQiw|*<5iS<JXh8K#NUwrprD}k#DREXS4ag7%okTWu1Cx7zn9BXJ0F$rE)A92 z?S15%dU<A@WR&N1sFO&;V>%80fE+EG9wG}<H5!Ph>SLh4Jq)l4_0<(AKd2`A{A|WN zNBg@1`xv4!GBVyLt}Kr%0}B=`P&By8S9Myd=Lx@AC$KF1(ewE`FIDt0Se}dY@?0(4 zb^AZWpLsuI$Png(eD>LARo{z!8q5#KS+izU&~QCEu9qjohjr2>)=7U<o<Rej8hBlk zRWtGldu?{2?vx!mbdU)N2@-oVB>QzaIXTj5waTSSm#T7&DIZnuurE{-E#y7h2G&*V z3$Z`S@c<u|=L1jMWchCxZ>*iA+Gp23#v^)pUXHTBrzT_#JIqy>(AOV@Z-sxCE?s(K zYflEQQz$_{TIIu2Pdz0^j2I!Yw@4Nh6-lfq$p;^NP~pSzJ^4)<*cPyzpj;6+h9M2C zPbr6N3(2E*9AWa~XNdm=`Tn|Dm3<791@<vmo>?b7IwzXw|Ka!xbAN?c3SCI~fvm5< zxW5<n!MuPnEa4`hyH%o0NPZ6;I#l(0updU%pTwQGGLJ}u0kk8(DSI5}uy4n_V0mDf zR^=J_!1mcF&#aSN%k%!NPqH8Qn8EAonSJ~AeGq$k)I12&*2}WQ9z|XxC^4rcZ@cX_ ziN3YMg?O;P;R>X|0D}&ijE_K>GU8_4`r)d{@~r|3+Gnkg!S?z2`Jr;_15@RfA8e5q ze*N_@^81G8AF!8F=I7_1!yYBMXwjly@4WL)nVz1m_>OU<k|ol>a>02Y;zl~E)519j zw!@Tr_K{dtI3KYc<4M}FkHmI@wAAo`1(%L9zy9p}5931FU5z=)6ZhP6&lTc{eWMCk zrVSc8b?PLscTMF3+YHJ)`#uI8#FzL}=1C{V1~ge7SVmYLj69)98D!tYXnQ#J=J*-% z@~7rMS+*$ukfk-)FZKz`DOSYgym|9fK9C01tC(AsW5<qF_RIs)U;t?_#=RU<vX4!< zC!RDZL!`}+FWR$D#XdLcl7C?CsW<i+-p?__U%{VpPoOMuzL_);H_ka@@0}{Yp`oGD zVzEf<PEq+lcZM-&plQgJktaquVfi5LhDkZ%n1OP|ejxMCnBM^YTyFCL+{mNqPtd&- zO8{-a!+e(KZQHgf8pt2c8=`zD8WIx|<*;GHlx$&5Ug1w(ljo#`c(WX^{-Hg`2$Uc8 zwYQ@june$FFkaTd!2Js1$@lZ~vmoD}!n~6cNOR4H>pC~`sQ!Z?gY5qpd?h|7PMlEq zAa5o57Ti^=$^-ISLf(`Nu#F<0>7T%F(!hF@JZ1g=$}6wPmtJ~FwSoWo*S}Oa&Jlo5 zPSkA^(MHY#?z>=jACTs{$BnMvG$X$3|FHf?d0fVCmN%Njh562U0dlJP5?Ciubt}rc zYTsDbP`)X1#GmDW<&t?qIbj}fK8x<g!*|BZJYs&ZJqNw(fj8?-t`pwqqwqK6l%}f; zlLiBb8|k79u`Jwo-+dBwmSj8a`Vcn*7>4x>>mojsAC8F##GQ0K`Q($FV_c16I)4^- z(x~t^`v2f}K4~!OMS~WD2AbqI>n60_YMelsVq5FVU*gJd;?KM>`Vd^#q1;oJ$a9t< z)EO&*$6vv{0)JQeXC2|1A2sC(>Eaywgb5QQ_T?)1HhAu8(jR4svQB%p0mR){AHf)D z)!)Ef;m<UT@h{q*Wt2;{L8OCakbGkO!Mcv^k!zliw_CPsk&iz5sFG*$+W^u{*<smX zzlq<J8OF!90CnawILh@``A*#VG$TH)?IQ6vfHW9zy*yzY*b}Ydp^PyMX(PUrt?j5g zNsECy`lnC-MS0h-uKZQ=KPX>n{EPNGpR|zwGz~gv8g$SkPg%dPED)GCv|~Q7?qoS- zp0O_CS_0RgNDKLnH2z9GQ;BiaH-*0;|L7~UC!Yw{%M<qR+5aJ3T$dwIwrK9zvq#mt z<N?bo<(>Gm96%n|A^E>6Gp-agBR`G#Pt+3?^FO44Z72ILtp6wnY>(J>lE)l#lK0F9 z_63Z5;5X}h*0rq1Fs4xJ8ld^#jXUX3^6x4e)#cpyHp;E5Nm=JN{V*>m^W-yWq^v`Z zuAq<LL|(C7<sOSa(>4*mKYDJ02kt@mPXg26-Usf}_}h=nL*uf2_Uv*|TV4sCJ^Lii z=agzD-qiQM&-BpabJI<nenEP8{-$ZfXT<M<cOIk1_YU1W`FG4*9Z#v5Zo28Ao3(Y* zq?@gDGgvosbyI4l8%^%hG6O7tzqn6}`+L~GB~YHP*;hnPF9cu~TwVaUKK$m2O7;0b zL|5a(wEQp@3`CnBm7JU$i~fEX=KMoo9|&Ndy9uB|P8s)CWm3+<TF;Qrv^6%)1#?Z| zcC778z})a>zbKThhXZMCfm>_tz}Rjk%5)j)GxRxsMSWY0w%`ovrK9MdKZSX+H1vVP z;J-Vd4f-2rr(%tR>tvh@wP601Yu;RI{p6gK2QVv#^GJMtg8yqhEm4QBMVe)-KUqg| zyhI!b#u|p+=f8q_^&INl!>BjkV8mQA<$5F6xwyW<IdQHJeR^KXgP{Ee)_Pm9p2oaF zBIcgP5C`_1IQC@w$a<Y^5$kI9W!X=m8{hei$66KFJh|4!H6HF?;2IUzcew7)H8wui zA|CdwI0nENGy~&>G`7EN*Er5)y6i`jCp!JA@1(`3{c^qRPR!kMy^m{Un@U|>YkcP- zma9Cd^f?}6AAvv|2&~@;<O$oaAHO{+pRtco>k^y~=QH_7tatsOt((RH2d?{a4+Q7- zx#nxgBiDPm&e$L3r&VRL726byUlY;K9YZ_}T$umt0}~gvKW{!VL(OS(&6#uZM*75I z5^&(UC)dxFJOT%<wQ-Gy^2jwRu61&qa2(1Ao_%_rv|>Asd6x{Fze{7=OfYa@pMyMM z-}<Emp=zy<>oc53<ioTHTzlpEG1vTD<&k??xJJXZKCUrQ9s{<ipcjnv*$*<-7ul|| zpJw#m3|tt3^U9nHT#NZkuKD6Dom_}A=86O5aZELN#QuF%Cb*Y|@>p%1t`*bAdP*YZ z6~?&Y!L%voH2HA7jcX)aFXTGamWQ+caLw?C-*8j=39NYn2kz%#nc$i&AA^4OD{!xF zMs99y8vCFG0}sxdkQaP7zs|KLu5oa!jO$EX-{3kK*O<7r!8J0jFU^~x!9N$JO5&j8 z5$mqT+Bf5KO`mlDfqff-D;~s!`M>kNV9E8aSAYZOG&wiUH5SSv*SWa9!nH=V#-*n} zKPiGqsWM^6;{fmhPeuN-Z-#Y<M4Y=E!@7XuefG~uH*p~kXnwplRjnIxy^3qMTr=d_ z^OO2|A<G2UN4Qp)hczmL2TaVhj^^4eo(lPA*}~c04AlQ=EQ_pnI4<DWjyz%ALw=lh zej(p~AV#edaDJNd$TfV<O&eu`>r7nh<2qTcjsp{mIiaoNPe9toF4Cr=4r;~zC1sH1 zkbQod#DhS75Qqo)#C*8kb9mRk)S4;R>hggD*GsECSJi(^-{Ej1KJmm8W4JcN{y6a< z&pEE<n40sZ#DlzGeMC1tT)*W$0HaLQB#-o`%UVrFEB3K5Uy*_NmKo&3{rBIm>OI!G zZ2wsQQx?b%$|BPyE__%fe){?o`Qz80p-fbhN0bT5BcGZQHsqh<an5saPM199_zGoF zjkj1fiIb5(u6e_}cy~pNEIs{+Jp0XOmGX!(!S!p(<6{fPG5H$Xf7Gq)Z?|IlSc^Cn z9L!$bY_&EGoeFZvk|k<<N1RwMvK$Z(@__k6-kftDl^?B{E?>8YsJ#G&JU%ryLca1) zmMl4q&Pk=LRbj)xfdhMBzIQI^z&d8;<jIrw;{3LpK7G2H2gV*rHFsf*eaLh2gZ$_C zj<P_05dZ2A<AlGDAzQ9(ZI$%-fpxLbDEDd{$hMyAGF)3iKTBfYx1!q^e-RG?`9VCY z=MC{=yT!VL<5EQ58^HeE^`2H7gQEZO1J@F{E`f8VlJl>`Vdl)4itnrs*bXvoLk5@@ z>jk5%qMazmy3AC_at``P)HTLEPk%I~YDHdw_sek!&mOMvaE=}a{w4E*>uYG2RXXes zknc>Nz&;uKXoiWl>NoK79>nz|)+>HQ+8he}(WB&#Wsq^PZ%2M}E|)UMxpb~;uzV0t zWA2K1z<Pn<hzohadYg47@!Y<B`~66`!5<|KcUAteew&DMbYqw{<77S)2j~fq&?_K^ z4<D{@BMt=mVHu!5$_@KTtS`7P5p&^d5HH6HH}a_Zm-P?!(Wf!K6PS}{o6kCjYYWg> zpw^gKE{Go=^1+znWq+A#D(ts|hR2cUjiycfRQiTIldlBgL121pkDwz#)eYRMO4=!N z%rEkqbhA#z+{@E{GHsPU(?MOM>i?SXF#5nab0BfvQOy;zU&uKp%H!WiTcuBWjrNza zM0yz~fps3s9LqN8q>OR@4)<Q*T!5+{{vzE>n@=m!U!Cu+{AV5zSogB-V?IMC1m*8X z%!d^s4$hza)rV(IeE%Y_eEm`Vc1^s>Tj9*ETg7?ZR(aqBzzra70O-#M(+WWd!LTzR z7w-g_SA!0gysOUbn#Hvq?A2o2H9nBX&?ldKaue2QE})M33Hw6+@$}PASE+Zf25=T} zWIp%YbIKlmJlC#W8;SYsw_kkmMU|gM8^(M_o&K3?Vq8zd{%6j!UPc@zA%Evt4mmca zyuO4nNF4fg+}9Y4vDIT32jbak#6iE5Y4+ia{)|zkSeGSW+{7^x=MX+dx27ldb>cDl z$AaqzOp9fW^%8;d%CLMAF+AZIc&pYWQ+E2#uQ0c;ZelqiuIxKdwhz9wPOiw*`i4{V z@f*jF9KUj`z_Cgo#!8O>FRrz6OitV>|4jGU1(B+ca}Hy$$AB~A;8>hvFV019+{bZe zAB;OWN6kJJ@n*fnhhrFyp<aDxreqwhPYJ46&gpO-fnzrEkNLzli2WcwZ{8cO`db`- zaO}ac5Bs_tZ@ln$p=2B!hYtZB%s=R!QS02S!^nq|@2rtq@&>5!B>V2{w{zUUvD5tI z!77co6H;!#xEANUWo~Y++9SesHRdJd#o)j4jGu!$H>!UBe2jhchs16s|IjX|dW&mv z+&{puhRnUZV4(cr<YC26j-d)tRr==*`JwEwu4lc&yu{gc#Z%VR%**4uo|3OD8m#tn zubMMdzW>HEOn$Qw9%olnUybz_<%ab(`&`Tq)~Bwx@SSbB5tb(X8~IP(8U3ykXeXII z+arz>7&q%>wEelR;aN`;Z^lDjz+IImw%MFdVpxu|*>+<srb<}Gv!M11A-(|Np@V>V zEinAhKfy%5ZkWh4n{huYDobiya}&@=tiGsk%^hyE^H$o{Jm98%QP-L$G#c^CtTe6F z(tY9!e!O&_xRn=maBa~)F()T^#^m(5<~cLcGjayBv1MoU%b7AQc}8MRml>&3vNLls zQ><NZ<ypVPoEcqbb#G(FWqhgsr@bqUuBy7i4<(SrAQ93gpe~;QAyAr}d!~Eln@AW9 z5G>dLu>_J}6eKJXB4SixsYZ(sAu8Gkk*0_g5D>y_5u!$9P%JnFjRP2E)H0+DrTc}J zrK^AXqkp<q-j8?Qd-t7v_xaAZzkT1jZ|yxXudwJ&Xo>*6Lu`VVgc4lGcHyuonl`<# zx!=rxX^mW&2Qv$y*CI5qc%a!%7#?O?9`r$kJ`cGW)9xvTz6f{c6<$5~<HP-%+cbhB z>Co40a(Hs&*(QuH96Y7CU{c<+gz)rxQgd>k(S}W!IDT?rUV<~pS8e}v@>Tmk`o@2p z-6a3SSCf2o(J<X4{~J%2k(!a3mNt0Uz|72ly=Zy=zr!PP_0a%v)()kjF=!@w3avx0 zql0L<*A92bIk*td!pm_DehXj3*OQwFC;iB1QcRvA)#Pomo17rm(lE6&osOZ!^bz_D zt)xroTKWcki+)6p(4#b9cd$}+l$~X9-1296HGh|1<WYVH{}$i+zw)2-SNX5|*9tC5 z#dD&M94haXGvztCUTsl()IdF4=jsAoZluk(Q|v=_tKDJi?NQrgTf10ygX`nC>*wxv z54gu&rCaKDyUXsnATH3sy#Xu?qPqfy{^&#UC_PIr(VJOMwuZgKQvLP*D3K;><!*UM zek)t4v1+l7gCC%S&7Ed~nPQ5}V`i>--ZYrQ=A`LulPt5^uC_JJKGfO0_5gVDmHp0s z58hn1ZCxi9=eoPT&U3y?bwk`JH{MNj#qL2@3f{fws@-c)-zsuPV>=8}(Gv6qYC!$G ziC&qvz<bC0*t-_T;#+Vc7I+9Aju+!K_-XP7vWy%d$H{pT0;EUN5;_m?{fuJPfyeT` zd>~)M-{kM}dcULSD#nRCQ6O5&RGBI3)W@oe4(mQz=u~~Xenda1EA&deLGRKh^sQ!$ zxz8Lj=S@Gm*1m5CxI0{)yWh=sFSsRcliTl3xO483YZJ5&x&^6#=Yzq#;L{*-b7>H0 zXCE{Ty@j6eHh9T+Fdl;!;GK9EK8WjaBR+x8;Y+v;=}h8DnDiu=2-2SnC!@$XQc9jB zTggRo4ed)Y6?7P#NaxTttT!WU5-Vg|*$(y*JH!qHYkpu&>@thu9bu(i`7OK;+!)e4 zg%99Ecm^NE$MS4mz<<Yo&lm88yo&#cujFg_CSJ$)@DF%BFzp(@oBy%@x!>rY^sg6> ziMiq>u|lj7JH;NcUz`w4;yT$?66xdsd8f>h_sOMlwLA!%I4A!hd#iWVXX=#lfrq1Y zkuK4X>T+GH-`7WgE7Z&~tIPrOrD-&st*{O_y3($*@7NgE&GmP8!Okys``j0<Q-A{z zOb8|g_Xn$j4Z+UfVgMz#LPfxXD0Dp<hJKB5PzibwEk%3KC+Kr@9MyS8y|Z3doR43H z2;7eM<8PZ$4U=9Zg^VO)NDf5e3*-}$1Q^a^8`%aPBjUtCd0DpC33{XcR!^}#T$20J zeed!ED6iLCz(K$;1dT+e5%zMuIbH)UCS_zWJx1Fx#LC%Xwwdh{Z_3fSQrCfvJr*WR zz0zaiR@58)5_yOri7aSFG5Rg~Bie>eqBw6oo=d7oC(weibP;`pzD8^5K2U&OY!sW% zD%m>N(@xyyo&9)!l9(yhiml?3h?bpYcbOzlsLQIY?x;KKcs(0<x>WDg-Aqrzj51?Q zHpJp9rpD|ryUc#`nQ3b~*>1Mhr3J4BJAyBQzeUzIE7V-$v<-?!nP>(YN(vxy_K}n1 z?<9sYnn|NrJim!2^Pzk^zZ=+*>JRnbZ01Ic7%hGfJET$LRFnG3opEi0uE8&Y5kU^% z_IU7o@aJG#u<y#5%AueE@IMO00UFD_dhfJ%0U|dVcfc`N;&J#PJR6tcIk+5G;Dxvn zSK;sQDSR3?fr?Hb_W|1TNCjC)D#<qT4e1IB+Jh!SG$8o9o}h+42Jui%E9gSHlbvTF z9>u%xi026$H+x>dYxov^hM(uH{5F0^zq23qOF>D?{dmz!_`-<+qDE{Hwc-PDR$LGv z87B+mboroMAZz3s@@@H{te3~-8F@iQsrKq;>IQYIN>WleHBgOES?W2p2ADq_lrYPz zH5*N>xnR23CAJl4Sgl(Z9E#vw6$+)nz)`jLFdjx8A<vM5<P4b)`+kg01SYL!N7yO0 zoDcOU`A_?2K%a=XUEgLtus?UVxtxd=u7-MrFbqIzf#aL;>v#v`nq7D=9!|2zbg}^U z?;I(mhiNoRVEtGgTgA??m-q^v0a@dIIYG@-i`2{ZXvEg`=32#}p6DL*4BCLIaC_E? zbzyPr26i*+&U!J;hOu#MHv0?P&%R^r_+6kJi}+^#4UhG21}=>CbNy<+#{a>O6Fo$_ zco_7eR&12p<X&}5&D1aH1NyLj*{n31fQbi8tPR^_$O%WR1??<_>{AUqi4;Oc;7%`; zgq}b(sI|ApJB}$)WCZyQd5A=E<a_KO`;>hF`UbTv8m{!S&GfRoY>3EwugII>t?*WR z>)cjAd$;?mt9M_!WA3!O=voIIg4p23phwU*pn(a}g7jcykQLdFQ&$R)oOVsFELaxQ z1&t7d$oQ6d_Ia#21iL(5PdYDCdqtcN_~Wx}}@dez=`ufYr9Fiyc)I2)Hh-me19 zX}}@S?-Y_vCX-N1t57WPK7_!UNR)jgl2i(5WQt6agJinQkRu`MWXWuqEA!=KIaL<P z8L~vqlCx!*oFmI+g<L2rWtFU!%j62s*>!TG+$y(27OsOl{GM!(U&}^$OrDabWs|%p zL#nlkRvlD~idFIIMio{)RHEvukfKT`qf%6w8l=)yh8n3dfw$QzSLLh8YN{$yGgOJ1 zrDm%#)d(>i4Z4!3({-lK(%EoLkq>I#V86DF_Lz-!9b61tO~kt!UD)+-iIBsEGcLuY xxj`=7Ww?<p(@k|nZiXvyv)pV~=H|F^R}o=d%cBK>76e)lXhEO_f&V)M{t5GqzHtBm literal 0 HcmV?d00001 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/t64.exe b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/t64.exe new file mode 100644 index 0000000000000000000000000000000000000000..9da9b40de922fb203df6b9a1d0ad4139af536850 GIT binary patch literal 102400 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�vlnW`{LoXpTySn$@;x8yoz96jn?+Kk z+8+$D$B^0(dpJ6<hZa-dac!=Dhn7&}Ml@L8?2`HV^PF4PZHDWFvVxb6nK^I63}Ihy z5|`ET>A18uUe?`-6oJ;)kr577Q&Vi><kIszZ_Qr$uhe^fsNOTolL}S>eB)*fB=t!> z-VTs@&yaf8Q*)_o#I@2F(QesP)-{|(k4g>Cwre=A!xmwMY;-K8yUmySJ@MCm|CJ}& zc_7=d`c9fRLg$#ug2UUQz30hw(;n+0IrXqxE4M$qE!^uU@ncbAh1*({4{wX~#v7|h zt!Ft>u19Wbj`B@4NCff}+~v}ieMORYgqxzJ`{&g7c6~pRG;{)TWAZbx<ie!<90O}9 zFuPst_a~ifU7)u$k|UHeV&x#2(^Kba%8_f6es?Tqy=O2BmwS}1RxI0mFO%57W>(dK zj`6KWJ}w7wdDMsql_NPa){*Rv&G4++R*ji#uw-r)A6qgo=lJ7HdO42m-T9`*@Q7Dz zf+UuVF*`x*lEuy`b}3{lL+qQx-V%G0F)qPi+bXtgzTai9vLpp|U<Ej8B$MXm=2j~8 z#PV~8bbWRN7g3L|ARg-t){(9tPfc@phf2~Fgk5B)l7bbK*Iq$2%`9=Gq9I(EFH}L% zrh*VWhLZT6_2Z6h6H2s_uv8FF<8p763W9KbOfoJW-bwWRO;=Bqsh;-w*7d^;>m{as zs0*o<P&JwQ+18<ca_sbkzHLHZ2%ZxQ>1V`BC1hJ-*Ccia#IBKi=^B#in@UCIn4Fmk zior7R+v?|Wna0Stc!;T_-oKJ_$^PsZYv(fQ1ujjn{%Vk9>tTa9t=}6Y#k$KNsn#7D z>0#YsEHbRM2Ju)c4U%UqHppyip+WMkB7-cjt};lPHO(LuR<=QwS{WMg3(c%?245?9 ziow?lKEUA8&sYft-za#b!KE9r+HRE6_%{pwp}}QbvYHIuAo$A$-!1rVgEtEPguxF8 zzS-bSg5P6sOYqwb-YWRb25%F*+TbFcu)GG35qyEc(VnQRIR;M_JkQ`x!QBRz*{79e z@KnKv8(fYKTTX+sfK^r>gL?#zF?gQfM{m$&uMoV|;Bs!(I%M!_!4DXGt>AkNzFzPK zgWoRr!v<d<_$GsI5`2TfHw(Vr;9CT*HF$&IOAWqT@G^rp3Z8H9je^fG_yNH^25%Dl zB7<9kry9If@WBRe6Fk}ABIdH<3?3u6GI)aEpI@)(kSw@maHrsJ7(7MrMuVpczQf>Y zf^RW6q5)++U~rG%8x5W(_-zKCE%;i4=L^2f;0pw=FnERFMFw9g_-un$3!Y=}wSs3D ze7)e~41T-dDF)vt_yB`%5<J1+n+1<F_!hz2mQwZ_`wj)?KT6u$dUsEH{NL_G91~@| zD8BrcqOvvqIb+jcY}f&g-)3z7Xl!O<bHvzeFgDy)iT}vhtP>lZn(kW^HPoJ7b}}B9 z8IJ`fs1ut?V>89rq+qka*o-$eXnlyk#@GxqHfh+*FgE>+O$IiTjZL(%@nDl-Y`$2c z3%3B9%Z$yt#-;)rx3Sr8Y?fkkp|N>N+vpAm%Mr%%cg9kBA}pQ8@~6g9x+E-n8_R03 zwCQfn2S5?@&$E{%8|J<N?zLgou%ETBlOOvjQ!$P}NmZf0Bp00|>s01iG?D}w*pSou zNIoLCujkrC>z{ZwSCF3JqS>>diBbD}gUJvNhszwO|GKb=>-u!R@M+)WoJJ?AS)L=? z5Ed)n`|GoLl1KkmO|I|Ly=2up0Y}NIui6|-o?|kMf)-fhY2Q=Xj|YzF--?`w7d<tt z6bwxfW1r4mv)zlf0#APyeg^{ESqaN5r}jnfP_{bH^lbYe0lYtWUmJIT_b}FxwTFFv z*+sAen<8|`KP&npuX%ne8D++{ejQ4q8i;Q0rfA0REx`=67XW4dOa?e$6O$A1tmnv! z*<<%-pZSuPIaOX8;ii)GC3ZG003T)JgCu(<OR;2WD;sxV9G>RyG5z-&{r9N;`?dc2 zmHzvs{@d1RR@x!|!$c8BOywC;1%1eW*oBd?zTrlv26n!W?Wv1zAnf3E7<X3oTIa^6 z9Dd%;)q1G9o|$O4_k8NZlTHj*p+10S$~45614{dJ$0qp3gVMU(vB|!XGNBYT9?#3X zBsf3%WiWhyU3WNtEK^u?Oi`7g&mV`5j@=%|9#mGE)I*%rxqc+mKEq_~bs%LOXxNQ{ zz;UzWjNN7M+~gq7z1@k?_IF^kXLR0bteAF&TZ~qc)&Oj0v)i_oI3Z1tYra3Pt(9OD zLiVYw*EyE$@voIZURq^jV1J9UuDlpijn;jNA5VS!ex`JRq_G~X>!L)txN#+py7=W{ ztczb^z)AzE0XhwT{6et_3^|6hUHof(zoIg&!vZyxr`1JST>jX@{{1x{aC!Y1@&n?X zCx3F~&+M8HBD~XUK8W;=tofju_msf1-gB)lZV^fUDR{P4gSK#|YseSu{&E)LV?eWr z60!QHv1$RdEW>=zm|qfenKc8Y!sgJKIi4d@==NhkIfYT@&*Sp%6_N7F(>4~_4W4aH zJjVL8oCvaxGgd{3w{XsF))&O&`iBp0agy2`liEx`@G?PuAnD^tvJ$bfMWdb^c|A&2 zb2$?c>#EO<u!;~|?nN3DgSxNehtP*s^L30jvF7U@UY-t&<WL4;#*~)fIsPX%NfBj{ z?y0#?aD`7_Z@2%^&EUIklD|TQmMAyt$9!?Tz#o4_0n5_;$V#04sHxx)yVLhCKZgfj zv=+IWHWGl1-`z313d31yr$0UgPc1K5<L_icvo3NlIg2J|D;`8ue+-_}-(q{=XOfnG zABtdFqWsSR(%(X<K3VQgmK|%7VWFHlo!|7g9BaB`bl2%zHh&{Ca72&`f+t{)d!!TQ za#1p5JO-LEL?5fHpUmA~HpbXJLYfpvx+RN@*G2t;<30et0Ct3;>e0~1Ywc&66Jh$i z8GIO{<u)SO2<qk4=V3mz_R%KR!$Q*uA4tj~&K!Re=5%d7SJGg6JAH>^^)H1`{<iF4 zM|0}SW2nm*$Lb$qCz>{Ari)u^cKS0_Gxa-t3?LrI#70j<BWfHf6UEOX<XuEw4Zc&g z?5qBArU9>1W{cY$QZBldi#*%7`&mDrO>dF?3F$3G+wzbkg3hd8YcuY=29b2Dar}vP z{JX?xIduDGKUSY95j9G1j);0g$4N{-&>~eJI!uln2rzUQy~nm}+sWC3`H1hHiq*yK z5lA}QPE`)t@{Ci9M4u{=YT48D&~`OlOdj&wyKW-Wa_^IyH!eTxIJuT;wf<5U{({r0 z?$cCstl?w|Vv_iDHu3^VZ%P_S>qL(E6KRt5Nxg8;R2U>~;&6jF$hpj)XKEi!*N7Pj zX4L6K8c1prYjIy<+&6{XyJ`0WLHAk4eL=|m<9j9imZ1CjASfOo;`eZndO3p(4Km)5 z8wdS41fhQL)WRA$>Q9371i24{M^^8b(43)|$H@jSr#8vjp^1{m_<C{iw+514#w-lS znWc*)q4NW;2td4yjMJrGwvbC9X<E$5^|&vH#T9rNFVTlNFU`qFZcHS#WDw^do!UO{ zxIoehNd-S=%Tu0O@H!9=F}8=^=)<%<#Be6f`0>ZTc8?Gt({@SV2sUCiF1c$j@K=Kz zp!UdpoS|j#4@cL7NByt9%GQ^yudT`{$rE(YrE#Y=cxqsWzkSNbVVy+Y(UZ*s6wiGs z6TNPtc&_F7@1vdW9bz)`!A;Bu|G+qB`U_Fz*eKWEQH7`fyy$b{0gb$Sn3ORW?In)f z8|>F&=~*e5^;3yVh8J$>59*LbL~LC8&F1ivZ$R#_gB%(OFUhQn?48YShuj_*C9<#P zmXw@f`!hL)e=4tRMP$o4B-}&*=Z%tS$sv6kAGiJ_=vcFe9#Kcxqoi)m)MFeR&1+dN zxo`xF)a=8HZm%5ub=G_tz9PQn%Lv~o{-`|8bG8ifM|psC@e{?=+j@0fXml(Bl|xn1 zu=So*NM`LcH!p-Jv2uLtcVwgG2SW5{Yv;A#b38R)H7&`6S*}IAAgfyVNN#u{d*Rd* z(&|9c1uo6I@RzUrf~c?WWTHyDf6m%-mp=G1%v1Aq)8fC1&12YD7we405tBLmlq&sx zn(05RK{)Epe82>Tlik*yI(E(@TK;Cm1RWxb!)EOuhpGNz_Tk#gB$}YPPa?cyo-e(m z$#dkh?DPhnFWpA4(DS9^Q4>dF*yD|{?mbt^Krj1}oY{I+4@vs0*D0dwAkj076LN9U zQsC`Y9Rr^z!_(bfd*03b#u@7VIIUCvz~DwS5^PEB&m3jmp9sdDh(BumDiYH-;hm?- zNghtVpx9y%SFt$sodp?XO7LDcJW&w?tr6sTP@2_EXI)Qse!>YNDzteUescUAV0#z! zE4TihQDNIr&Y%BdpeXH?^J#gxoC0^P^E%RGmXMzjemJA?7$+{n^|?#V!E^pY>udUj z>#*B__P%(u_dTbz_g&kY3V6;Mmt?k`o((5E^-&{8WU@c93GdOOap)net=oRC`*QU8 z&0#j#AUskZ6@kwSuIM8<VK3F;xNSS<#Hc&zxR(<ONj|6W4?Ai_6c9){da+VafPL3D z87SstaXPa$7Z>rerWU?i$Dz&YFJsvm#XW}v?qX^wT8mvZ%lhUiuaoCO`{u088C>9! z1;1L0#!**I5l*6LFu#Qx?|ZEKlJ$<jxNNF#*!JuQX4<?Q@uNQwzn0LOpm2dT9H08! zz@Np9qo)JxopLxtWcgN)pod$;0~^3;`fsYDQ=G%PiMNya!<A#4)z}wh7d1*K!=g&0 z{eh%sIRv5A>NsjIlPCzs&|EawDb#yLPaETQ%aKg>+bDn#G0VS$_oE0WM3n74be0eM zCIZHhmnmmEZbsL#Vn;X!uAuVGOga?jb;QZ7*8F(Q9mhp%$`(j^<suXJPI5{-v%?%W z{~45f!*1U9y8+G#)0`9TJKgbE1ovUM{}Nk5c`{s%+jhtiOWA+Pk@+T|gZB#^j|tIk z;y6Z0I^KQrd9hXwJ`A$ePWktuzorN;g&{8_DGBSM$nNeluJoN%XElGo83qpB)+haj zd0}7|g?Y!d*6qlFE#GnNa!2P0!D*X0&0lKHdhtSx2bw>`plw?~pl~6fg%FzY;pAgj z%P{et=D6pGL-XKi5xmVXJ>Jd4$?d6s#7T?eu^aymJ3B*N>mmnd2X<T(>+7-h6c-nP z0y|mq@qcYML05elpdD4d;2Fe@l&5};ghy43dsu^95MxI%{`LE%?&G%UtC%vg*EKEm z?(x+BRQv~$CNP_nDj&rNKyBloBk~Mu^%OFz@}%!{+#E;#$=Bm@^2ub*v3e5zn?Hjd z(&Qa$4g?D&k~{73WW^f8phmfBc7#g>`*?Tr6c-gHcZ~JW%<x=N$D|tLsxOa83AdlT zgkRqG-6g!5N|F-h((|6Igy&F@&LvEj63Vkgy0R+7hkid?+9;dE?G-iZ_!7mzZTcxu zIiet+y$8l-(k4h=xwB)7meyP`jJ61nc03arbIep8mAzvaigEV0p~E&<C#G?Kbb-_} zm)eXbI?*vN)oq%0;R~Mn+v25av)FX3k3S&uFjzqR&}&G|`{QEV!*;U%AReX-JtCOA zyVH-Y+|t5KU!@60i!q7&Xr{pQ1FrOAj@1zmL~{|RP}ax_g?@HU&DT-BORZ<v+%^}8 ztb?HFq1^H%zLOq2U!rT-BR!0VKA2@IS;*3Jy+G1-5|d&)bd*V&+(v)B<sQa`EyU2g zl<+}5?yU<ExlNA@8QvbQ>g5WZU1RWKU8++aKyIvj&>E*|9*_Mn+LY|N*2^#`^T@Xf zB)$!U*D_vmcvObX;C}mDo^jRk1J<oE!2_|ZFgX6e;MsmZR+;#%1FM3C)kkj3{n%N| zXgl4HorBfM?#K2Sbi(~u9@OE&SMN4xA@^e|L#bFl1f^i+e(Y*O>ie<R8Cxs6-Qz3l zHQPtrySN|Q=?<9Ib(LVnhJrW$O6n8^S8IUj<Ja6xiI&R&ew5DJ_rI{ROT<|xx&LKu z{EFJDiO$gdFS+qM46DBk6y{?kvJN(2?!o1@jZB8j<u;fn$@&8sW(s^0z_5=g<DJAs z8GBm`Lk`wgD??>7%S?SQ43$GU_Pn#>-Crk~in#piKbWzunb{=TrZUz&A9&8%FPF<E zpy7PY>ns0Z&$7C_>-X*YhR#_kO!Zy*hQ6e|ev7`LtOD5gW=h3M##5KIQ6vh(tsAkh zB~LNh?L@^rGCg}8p{<K|A#8m^`?&_(u5W16!|-~2(#@;!lfKiN(m~(QCOl?;yS||{ zcnRqn+W9^84eeMF%Jx*yuJsM=Y#vOurMRMQK<3c9&^d6NzM(8s9tHIc9ikU&*EbYY zO(6P){(?&<eM67l9J8MDNA3EC(1IU_f|CYL=udJyTHAdyuC8rXq?=k{_SP2TsFg9y z<=@S;*il<Z&g2;A&4`@@j<exsw*B92xWR_CWI+@V4VJ9@`xOvHXV>iduPPuKTq|^Z z&Wf|>SO3POqm$Yo|3JF;jtYo&eX9bZH*0isT`C|7h13d&rf?Fbg94%_Xw_6cW6p63 zh%WhmO##u%v@7WO?^HlEL8{xTLN2BW#z@Z)iCz>gto@f15NY;l87Z;qSlSg3U1Agv z0YVChz)?VS$^U;85Y@2NM-RMN#8}aY2#!}kgiZXXt5^UF@j5CXsvQL%{I?Vk{h4Ta ze@@5Kl>(x5+I>Jr_Y)Klop*={gVBpa&-Op6fM_Li6^}n_qfF{DWLlipK>^Vpkx9U7 zCs9D;mTAtvrhsTUlRVuTzK;T;R8kkuwgRHVZccliTmezI@hxOLxdNhFj)VwJp*6E` zD0;W`<!qf)Pyvx1+uSnU4rw47dl6d_>(0;`h|Xnl%7EjIWeAE!?HP3Vc7;S|%`y|$ zZ&OG#9)&jxyd(J=R7kX5wA=J(sjI%QjPufgr^Oi8%!aur!bxZrksye(xfo=fQ9y)J zqCV9(h)yC|v`7F)vy71FBocd664~~sBpNC9MJUu0Sn8-fU0}JR_7s7rB<d*;l|<bH zqLN7N9EkRu{Nq&;ox@<-l}e&>&=G6s_?;?=0uO|0rFj*pKm)CkNcW%!rnE{TIsDFJ ze!>Lq=1L5mRT5pJJ=iLVrUs)yC6VqpX;)e$QJOf^oLnVQvI+PtDv1swU?7H==8p^# z*?h<#Va=}#0@0(J_ZzG5=G`DdwJuc>nbH`QM8C$P<wA&S&m3Ry+A4`|Jlydvfhd09 z@UUojN@G4Spb!0*-1A7ZqIB5q%=<meE>S~^4_T95B+Auf20we4K1-Y?-jV6QcD79D zwNj#3g0^rUnBxrFnRL$oIdO0wLM{k&R7xakxwPZ9bIhLYcdWT?rX(zVtRkSTq2g9@ z$(n0%`uKO*JE~fAoK7MOtFG2u+={g}B24I@lc<;0s~glwM7Q74*ZPJthW0wPmr2=U zRwQvkE0!b!tgrAQIeM|(>s(Nwn-s6py36bl1fp7Rl<=<^RkFQ*jDOJ{3kl<S%_YWG z8QanuL!{`I#UflPLp;gjExi2ZLA3=NVYW_-nlF#};;qX<d1#pkNk*o}H~tio@IV== zHj~}}KmKZ&)QICrcMQ+JWVH0M9HHRW25xO2GY{?0C5z4#r>!s~1Htn7*Aki8YD*PW zv*`SFJoc$$&6+St<XP)4Tv_7r+vLTFjq8ULkTk5~Jy+8mCs~4|h~X#V52h+KuP4-E z?C^R*Zp45$S|W1%Z}M_N+TfKJv12dlI3?hfIu<+1+>TLZ93SQAZ;z5k3LY2I5@oa< z<!CW{b)U&4FEH%0KEePD=JM|t{g%At$vd9e5yRg&oFx+KI*A7X>R=iBoG8zG!L=m{ zJL|<H5%@_jzeR2yH;cy$kkYNVSWe6j=7EQ$=ofT|4tO4^h;}^6Zf6539$I>GA&soP zYBeGE_U~pN$op5Mx`AiX3B7EW<FTD=zZFiA<0QxGro>R^XAw_H-RPDiPvIq4Hyo>z z*DaPZGDVlvv2_}a${>9!{8h4N{q-~{Ioo|A64{$4h18EZm+2GWTnyK`wEXeer4#^m z*c0ZtAbtkfM>fu0udVN)BEeI)l*79D&YAXmW{7Kz9%wn?%DS$%oL%YZMk&G&*#wD1 zBsR!w$>htH%mRIzNu;lG@SNA#nMNVX-*1hcCL*S*o3bt_?BhF=6)`tiAEh7>?zG}I zt#r4wX5((C`HkK$YWaiXQ3pdxlhuTsEaT{XC}3`r+g~TQQCu}&hOJ0-rGL6?n5Y4v z0Nfb)X#*mG$GhW`jy-sw|C7NcAYzJMp2AfU*puE`d6qkEewf$k*A|iqN9}oxX(SPR z*uMEJ%+`)_={U|I&J%Jf%k^;lh``Y30MdD2gOy{>yL1-(T%MJC1%6LtAgP#~P!xTT zq2)gI8Mz?VT+Ps;`;rU@EZk;wn=08r-0yZgiYlbF8RU2(qSY&gsA_INqFLHL?4KGu z>|e7ZgWkj5w+zO^CBP>%AN=bvnZB(nz0;>yTfUZe|KNSpo%y1;%iTfI^moZ@9FN&D z8^>cJvte@n{bV+ry9mjE<PeY+;e8+7)tQ{&AX670$L*lJ`ZUrN-Tlamd`Y6cy3f`L z)UOsN{Y~FdLxJ_)@1ek2s<yK{$;o<->-J5N!gWyxive1m)X4H&8j99mz0_03s-Ql1 zy-{C{%AUb_@Jj_-J2EBFI;>^KNAB~N@wu%}FM}7;7U~HpE1E4vrZf}_duH%(nYf8e zX_IbF8|~&KOge|<ChrMWOWS=8RRt+r$IJ46mBsx)L^4boGD>)l(@{GJf5>GT5I@yl zc7?nCnnBrU+RCZRs+I>u+Gto1@|hCXu<ca-k|d<i?~FHgjz@E1nobbWY(qrDuE}5V z1TV*hxwbVUrQu08S}#(XPb5PB9v*#j)K0dgGP&4^l%~x#-)qD5Hq5f&P$R8blV@jY zYY{cbrO+SuVtD0I6R53KMen8YXjRcuHG-;Wx$~n{ML(#K>5kfM1l8)IZzOC@+m+ra zT3z%8?T)%=*)(&bY{UK32z-hn*fK5|i+Jepjtn+f7#Wz~9g1<(9H!Pwn48WC9^1-k zPQukzGo8aMHBY7n7X)=pr|K9baF41}&^zs}FMs(CxxnWZjnnJ}wA|u$Je7u~N|_G2 z<yFhmMpa5BJ!kcO#jH;JZOwC~6FRa(>z<Ct>hUfwb9>M}%@D<t1a7q5@8q%tX=l~G z>!_VhY2Y5}pB?$r-o8#^6ZOwO>W6oC+9{6O1Q5rgXup}c-YTHE8_^CbYj&EwDtCHd z*(=Q-$eg%CqOgWmrtSJG2|xwbNxCG#l)dBFT`gjPwcfpU#^B<d!fNS?xs$tIM%p4; zL!(tCYbX&UUEf2(mbFBd)MYiIqPj85|4s99QrwlbR!5%|(Xw?u>1<Kd8P7MfO1-+# z-zsWKMRT?5DXK@w_L!&nSGrcwWG%CN9VmU<id|bv@c_eUHOE?`^$skymfN;>0Y$5I zf!1pMdkN{Yo!%6G$cZKNnlFgF-u0&z4<t1u7pnfGLK3GMWo8){*vA2t1W|k45YD<4 zwb$FxgGlW*|DNf6P^a~WMyIvWXASi#o|K|x)t8Eb+v}H=)HF<Guq_)rNAz0@d9P`? zHmKnG(DhTbZ+DOPc^kxNZPj$Qy0Z+`6m`^ALlTCSjI8=AiSk_$I7OrJI-1?7GNEbn zdXXSPN~7^wN9g)%DlRM<g37O(coWjrdQGqT_@TH;ZVT;<k@))Q6s1iB6<`lA3b2#3 zT?Yl&?H)U7!Tt~}*iBjsHnASB1-m;BZyaH#YRWM=lUN}~DC>W<k4&FL3AVFKCD;+J z2wMyGOF2ZtM(P@gLzsjb32diE+-u&4|LMYMrQO|W!yL5{(w_%7oVd%qc3ATlGzVW| zi95a7H^HSFdGpVy!}K=SQ+Gos$CNgS4YltphPJE0{)X)(`fL#_IBFN*T}z^58`aZ- zl1jw(T%U}e59@_mD&nYbj6Up!K++e;M`W;&<6EizT<-hs153e*`3^@)UJ^cciZ1M< zwk~X*S2l{S&mq!4(!)A}nd|8`0*$%_=n@3AVSlqoJpH(%C*3b)q@`<>*dH>9Wsdlq zb<cjyOwun!nZ8hZ#pVybq9-`KBh2gA!eUZ4{W;BF8Ov~QZ+*p17<Nwm(znTJPD^)+ zo};a$I=O4kJ;khHN8H1X5KT}UwtqK;Ll=p4AL1FhjV&Qz)SM2&s5fyWP>i=fe<OWK z+S68HT08UtJji3yjBtS!1t>j4X|;4hrsK(B@6>ce%*}b%;-8=E66y=xD~5=qVyPTL z$k)d*tV1livm?Hd_H=mV<2u_dBQ)SHxeTK%XNEGWlYdoK&y?J0oh#YkoD81Uyts6( zWY&9y$*i+IvwtB0&6;?1{Y4xk0C5`LnNEiwf~ovTtRdhYe8~XLa4p_UfaY`Yq~kE= z7Ew_2Oif$1H?O8`fj4t&8L^_rBYEp=keaq7jx~vdpe=vcqd6L^#C1W8b_;2${xGij zW7-K_LqhX=AdE20a^Fb8I7(bKY3KQP7`0|;xY2qYsqLE|#fLbKwH=M-YjOPC(R>Gv zTV%*4HHPbTVw4Wq7)8Cu7U_wNQF^B>()-qX-ZeIJv8i`CjbR!FFeVfpFc~OWg-~(k zC=F9hIykMZ-WvRcu7u_n=+4R7H|O}6327DAy1=?ylJ+a3$WhYsA7j+?=*I}PT<NiA zY*p8u<^wpp{93N$X?{*;RpJ|N>$>g`pDki~Kmye4Me8;EFkA1)(it1&*TfrCeqAlz zs4y35L@%ysJJUO6tF$R9?98n)`vB>q**krksiav%8q+dWqHdYQ->{Ypp?@@b_G<Qq zw~Tey&0H%qDU&}z#n<ncl9@?Gg0K=iq#xUrX`+r;ripr1xTa00##*l%v$km8c!ZjI zhMBw3UViwW_W0{%dMiuMsS~BKZWx@(w1?eznFe><jkkRBLNnHI(5_~z9MAisITzP) z-Y;{0j_9#4o}V7Sy%u4}Tokq<nAiu-Mz&}#WiMMan6h~K0&&+x(E(q2&G^f%L}86@ z2n9F=B~(2)McHpFtvt{Yx0QkgTB5en=s3}^D_P=^kaYt%OUFk29`<o<nbF@tYn&Zk zXzX%t+2mI%Un8H_rN{S|e3t5m#zzd*`TSaI6$y~f*Cn6d@Cu)7s%Mei<N!53{?}JX zc87MzZvQT_JNCsu(kUjx-%!-J2xL6uxNm!ohj*1@#7wrnjueqIC0X5!FYE95!J@Ws z#B}NC^!WT5vVOLw5ak5%W#Gr_a6#U*?XWw6oHKMqCLld~f*7EXbz3MVYY?cGo{8|O z3mJp!a$Tu~oQ!=}4k4llDx8!;QoeET>k+nQ+1NPc8&^N2yETE4biFa3%3^tJg8feX zcEmCqMSt<kuLAC|app<+-m4hJdBN{ef86`{?U%rQ4Bq6-fE+z%%t30oY;cAMD?B2A zbD@nEzSAYXVRcs}CYl7UQVlsOGQ*SH2goo@>3!ait-nT8_ugHtH8Mr}cj_(?o9oHh zdJde{NA)HdI-J2Zfec5zpWZ8{@W^EUfL0M^)P72HmsC<Yq}J+*Qav!HA8^#{!TxY& z49^Pw9<1g@h;xxCvQr|~7K1q6A0*Cw<QCya4|n~7FquH6v_u3lZ6bZVQrh-MKBnK7 zLZJS5m8j8@qRqWAGqt625Kj|!;^e6t%X@NHuHs?5VPtg6-Tsk}TX*?KdXBVkq=ssY z%tjcq&6<!C(d6H?ttkrCL0X*{Px;ntM~M1@6#P`eHxH!Qxzpcwtht491a>vcje^Hz zRH~o6+uUWPt4x&zQ<bOiRZ=}v%@nCFvau#=8yH7H;u^WJZwRxk>eQVXB1}!)nbAGA zCO?j5(J)&E-_({@w*4bYrbxU=Vk^6`wcK#lrC}#h(=a51S1~bTg63w5`492D6?!Y{ z`4!ABC=fi8FFa!eT(C=qIk#@kMl&%-a>FTJBQ?uMH!AvqjmXSrL}nr_3m=3w)aG@> zE2fB=?g1|ULxh!!ldL&cw;vq0<}ki*Z(*@ClXXpQll9rfMtbI|xv`IOtobuBpj;|# zD`FUt!z`XGNB?_?GMIRz7;+5b1*`8GBnI7x)kv?buZgsJd7JVpkq?BVT+&%r6AeYm z*XN~(37Q9dXIpy+Q9t(#j`7R_LqiiXGTBz@5W0CLW=<`NJa1P749x49Z4Bs1+FMpo zrq>*Wxu!UUA@fqi;91D#WYa)vK+37lN(&x%h3+_^p9XzQy@YGMy=#`IP~x!?ABp^i zbKSq<URk0{U2br!>96fiD0_djfmpF*TA%Bu06)9R6y8yL4@|~{DwMO^#9Z^C&J5kx z2yH||QK-LKfK0Rksrgc;evppW>g8oTR^<M-<+*xpYM@<cx3!kG$Gl$7R%#x}M+Xjc zZWN(&nRG?3d!3YL00mFcDm-@j|AvYgTT#z6O54(R*a59&tlo3X;n}A3j?)jgIpU|U z;*`k|zfM}$%{ZCN(4Nro3^@w*$A0AhD#!n+|4mqpj{ch~L*wo`(B`kGsvQ3y)W{l@ zxsM~GK~AyPI+QM~X6d!cv*Ibe$w){_E?5xm@t@nefH{_*Cwy<)<w-yH15Cd1vFs9k z%$-xwd)sm)Cp=>idSB4f_eG!~q|1KkVxbSsg3b-qAN8K@)|-e{sR|lHgSWqD*e5cz zU4wi_R3ft_&%nZ6iw2)H_9IW-D7tE-X6If<f}|5X5*0NSGi;wNak#Dfe?yFQQDyUJ zxrH00Jzsylb?dJM@m5%0UJ;~$-qH#2ef!-p&I__k@J2G2z9mVYS?`IlF81k2|ADwc zQlZ7CKnF`35Br>2Gqu&vczV(Hbf@vOU5x&ZrFGuy@psD|_MyjrEPMYq@<?h$ZXs8a z8@yRh3h8r2cYUK&z}P$4u-QZ6{Y26~@q%#+9JRl}2WT89+hDX+p^m8b_c5BCd$pu8 z?3mp8I`S6d4%64Qa)U4Df`v3ih8UDPvF6G7LRn{BDE)oFTat4@*%!)t8Y9{lL>0yv zWVy>)C)1z6PVMNk=yZchRz-tvqod|liOV1VVF|OkE!32<{vhS16gRe1l9QKkuyfMP z78JMlioQ^}bt79io6j+}uL;4^R^dn9s=p*WeV5~AFRd+ylUyRHlHb6b^ae+b?AaQ= zp&QFF2{5^rmRYzjr*80W$hSL&XsPM~QgmOyE{E4A#9Q~PK;X%6$z^?9H`b2f?DYxP zk;9qd{sY`|r90=b?>^nE=;gv1zNsQ^uP~OE(3hX%iFkz!XFIa~7D#%kryf3gvR!CB zHY?bjO0#|~%@C89-q>@&K$g(fcGovDuic{+EpoAws&-nHm$O|f-tINtnty?(PIpi; z<N}J;1?6=4zvPIjbrjE%wG`P2aMdo0IL$W@R!R~(XUCm^Gk40>Dhe5;cTc+7BcKn- zlkS5NSSxMw?{{sDgiw;rJ<X!pjlKlRbfx!O&uBgV55@*W=kX<B{!gt{Z<}sH_5$;; z3ukTnf^{vnvhnO4@1f%v#yy$W9Y5MeDuJX;45zNAL}g3;EI4jAr<emtcgvhccQ(G! z)|Jr6T(hyJfckOivfp#Eptu@M$B(OOrgJY?e<ywf=WhS@?DV%h{=a28a(7{U_g;n^ zvJ&UH-Q;9N|CZOSBx0^fi}LlbT4qXea`N57+XeE5%?s8~bXLF7;&R&KZkc8u!PGMI z`l}%nk>ly6CR0fBW(yX7U^#*3K+*;-C>=TA>Ce5$x47l#sX<sY#9AFn$oe@bg9PvY z5UFhYB52h|Du`TOf_=d{I~4g5_?_8;06o{tDdgbeX>Tu)zCu4MnQcES$%TaHCa!ab zrEwZ^PFPHMgKO2hPzI6vlZ~$ZU!mzJgWFZJf!KOQ^AV~gyeR_1mDec{ZJnNp)j44? z)yy@YL(`#3TZLJrg|(!1vR|$3!&{Q^6&ACyB_ZfeCa_7XMgg)fTI&er5yB$}`!@_x z4GN{XWI`%5hDkQ#ZHf#_eXWui!7kbQY&w^oPEim$)*tmWQi{4TC@xC}bKmscZ4d$$ zkKHPstV0+NM@^kWA$WC9v;E^t?=xKWo^b0iw$QXHVk@^)+L@hRKkG1w*c&D_prO@? z){_|jL;-s=C~4S{!X*C_`Zv}CdKhjVBhl<FVU~v!m&t-ZzUeAq92QY=RyG|DHZK^P z0Hv4xv6dm`<h}@bxO^c38l6kt0ip6K$ZL+rkj9dls6!0)1!aSigO7PI!C?QLHLsP% z)h++@WK{GWetI?9U{iG|*!fT#D{$)6n$wtk9e$d31+8A%^$ek8)7Ze_r^CGc>TZlV z{B#0x7A*C#gLjT`>Y#3VvOhB!x0<F;YIcX$WhVbtMDo5-){oB@+BBd&<bb!QwaS?C zG5LDHqch*qy(X>rIqQqR2LeYjS;ES$iwrvcVIKeVgkjevp!Sg0u_OGMgV7eoyB%vj zTG7w#e}?d-jTEP?Wme6zPO{K^5q%G@iJ`Lj*fH<vau*k&W_{8TFU8}m2}e$}LduA^ zr(2>~jQ)5g1$A-ltgm!A`r6seZ%TGWVe5y|S~yohC+=(+@K-8^kzmGPA|+`oS?XCI z?33ivzj3U265j}0^z1%E)Twtkf8M^nlirW@BHQ14yg63N&l#LdydJjrTdm$>B}qNR zjP%rBj%Ih}j=QY~7HcB$bil5bWWx#VgMu}WomqK^#Po0onP3C0vR-%<LlFu(`NM=+ z&j{td2Ng;Lk5i%yH<L)F$nQl{y1J;#kr)u%6MPdTK1*A0!XQo<)Z2}9T{rC|WqV-A zQqaJV8}wf}>J00l`;SzebnwjV^Q;T0?)60IL|vhFq4468Z#iw3)Q&D&$%V4+z0L#* zIoZ*rS}o^wiq5z|yZ!zc>y}@1;ks3NWow!pWhfsKP+w-Y&hOGmhZ@kqNy^q<I1}_} zMHdOy{I!}CSs0ng+<~tA=pBCRuU$EL3UkRD^Yo<EFs;Ym=UL0uq0~AX$K{09qO8g; zqw>f9ak`9`w=*~&R|`^lsh+DoX)k~sbBFrlJ%Uwf$le+pw{7$DuX8=tEJyR}SQeHQ zHOt4LvDs6|9bT>fmGyNkn}Hox#zy~ZtkwLnN%6S4ty!M0w6clcGmAeVqt>4ME^Pg| z8P*0nvg2!&9#L5ZUBzT18<RRX=|YE!Y?kR~b#RxO{J;BG?Gy%3>PI%*Xv06+aJLN) z+VEo=Mh-IJoHo43hSO}gz=pLp{DlqwXu~IM_<{}Jw_%$Nm&rRr{C;4=;j;71FU^K- z8_uzz*M>LQ@OB$MV8h)ueBXv&*|6uCCf*bqUSz|`HmtPaY8&2a!%a4P)P_53*l5Fp zHhkZPZ8q#S*reCThNs(bqz&CR%(LM<8!ootY8&2W!@F$wunqUx@MRkwvSF(Y!-tsk z2iWjD8|K(>o(-4T@Mas{Wy2?I_^b^N+R(CLn+;>_dOO{QBW;*r!z*pL)`q%r`P0X) zm!SD%@FJ%ueV6y74XpURZC+zw=Ww$>F!lomb?x5K|K@97zEaFGwC3`)azV2-!qBZL zGfbyj?KZGb6{;%y%v3YfBsD^=HtJu{JX)Qna#WUWEB-E1*(y^_QEqMPQ66=nTFAc( ziAQ2GzanM+4OdR`VE#F^o!DG&|C9XXs!`g0l(v)5dDzL%WkbQ;s+gP>lg>Q;R*IGW zU8c&_;x6o`;M<E^(D!uwRAAFN2AA@w`FvHl$!{`v89535#Jq@5!Jms*Dpe_d%r93J zU@lb1PV{dQe&=E1)Ak+XDIlJ9TT>ndH6{NFdP;s#Q>XJOWtV&f^J5@;%TT01=g#EA zn1vc9UGXRBNIV6^=OZUYb_gu_P13n<x0^xE#aC4)f3wI(kuLf1E;I05+`)G)`Crr_ z>~!3ZH%mT(=_<HP(o@RBFJ_5P$|3Pcz9l>(zvse*3oon+F;o%1;m%xsPC>5chcCkA zjw&h|Wej<+jmxl(au*jCFPv8#VwB6u7awPyQs^xg=jp{2-k^iaH{Vx9W+(f~bmWtL zi*&`iDk@8*zH<vI3q$o>Jg?GMP#LP=$pwqFk#rSlDdLy76>&;@e2WNwy&^7&M@=f8 zrwF5_7kGV@ML6)AR9R|c&}D(IvMOYPUd2U=iaS`$@EKEX`6BIWy3d9MUWrG-OBjVe zg&&2xB%#8>BO;@^MaOiH?GYE>(~*$at9PHIQ~D<NJGK9S(@r1g9CXH+gNK|oH0A7b zh7BJva#ZT*F=NM_d*1nJ7hITr(fEreWVj|~y0a!-;>n(TX-@8CQ}U)xn?3_^>Fg`6 zylT$X*UZf?m{(X-Trz(_=?|_gTezsa;=0NzuW#{^rPtqZ<Fe%|R;tYM%B-cO-n`24 z!eXi2h44}#bzcb=6swVNQkiaB(t;MjIi>h0rpJ&rU#a~$bvfdd%yWc)a%el#`Pb1; z&{g;;n>KdINnE90nF8;*w6CsWm`4AXs>bq9;v7LdE^=l15$R8yeC7O=9z^n)t5eJY zlTsCFyAqxAh1%}|N>l|_z+VX={h9PP(l<&wETz=*F)h;Vw^a9S9pe)BQqnBNy^Oy% z*#4yT#Ol^%;un8KxEE@7X|a-~lhmYNapJOwzt@vTNmbk`_$M)%5T&>*qCT8ZMoMX7 zU5KspluqsQTJmr5?xj^r>7^E{h_OujDOV-fNqkbL!IDY;)J~@nnobKzTl!S#bDS!L z5up%Mr8*l^3Gpq$EM=BFiH&fA&{IluBBd7TJPHSyF+j>E9M&<UTjxo7Q_0szUAw|h z!TKH(s_#XFl@b@@PpCDYvN}njg0M0oNvtOJGN37~*h;*{Wg$L;xo_u8Nv%D-(V_IZ z<}Zou_<EN#f^^R$-!m{v{YV`c7ei^GndEcizlA@7<q6U)HB^pZo`Ur2ssu$Lx*KW) z=`n_RNU*(0?HA})V#+10t*f}4ng@lCgL!JF?-+G{D868RzF&PVA+4@c?7B`39mddW z1oQv>)037ZZT7#C-npUlD&SGW1B;2Pk~n47Q3lmcY)Qek*-o)w{>O#l`~LZ#OU`Ak zw3Kwcv|*u8&?Fogti|@!g7rOGT@XsItNJddR;9j7PblrXT=y2zYZlW-O0QL{V+it5 z5SsFg?!-@$D~VD12h-<B|0TTB)ff<rtDT=lci<<<?}_m(gzC}@3MCfk`Y6zSo9QF2 zJ!v|@{Qg(?=lGOljwdwlNbPodx3^QN73np+^t;mEE+CAI7BX`V(%kgmLaF0p3&xk) zq5i%<zewzcM}==>oR<;*_!JC1QajY&_m5v@<}$Yr#$44Y?)LgjRqg$K3H4U4X(zpm zjFlCb1(#7kMmHHL4aH>iF{6VCBiwjAjbsfbzYJy8Te8z9zovj$$BE=we$}?w%xaWM zwasku=wIV8<DcDn{j0Fey==47Hutv84c{359Bk6RJlovQHY?kFs%>6>)cEgjo1M0q zldSsJ__c9A-8Qea%>!+716w0u;7;3|XPXDv=KMC}zSK5n*yi8_+G(3_u-%nyma~KM zYdd1X%P|f49k9*0y6ww+K>CCdT}PK=C-r1{y-YQXe@;e7X{pj8&5SY!Ojc^bB_<cK z_MB4UAF2QGZZUN&vCr2&WzHd^k%>X#yH<0+68@q<jbG<fvIuA9t%Ha?mbGoi*e(pw zD}@-O$0(x>$}eS+udECdv2r&Rrr7L@=%%`bCq!mtWkt#F+*INckIuBb+0ilT{M6K0 zdB~5QPR(L5`3v&-DYdj>DZdze@G@ge3?PPoV*Jk!3OG;rmqI^i{+;M{qK$`|`L=(x z>coG?`(LHn|AndV_pf6Nt5y5IaAZE;=U*_q^FKUrLjB7K_&>}6VXH#_j2is!{u``8 zfvdyX|AmKrzkk8-Cvx<^YV%z!KvL?%zhH`DYR407#sA-3V&kK;|L<4Yc3%mq9XD7Q zy=(igde!Qh+BG-*aBbZ>|IPJ3`tkZ(ZvDw^KmFOyZ@=RgzufSvJAb|LH+S8A&%O8E zzv;KX`~3rd_~V~8KltZ|9)9G}$F^*J{4Y=Z^~t9iwmrRl$1^*3?cVe3b9<kEVPE6^ z7hih$Z?C+1;I)IVzwzeZ-)egMop%qt_x?Yu<_}sv{OIFPT0i~gXP<v@_(<EAUwwTP zRowq=0nNz;FyBi+^S@pG|Lyeu+w}h~0olpjwSeq@yZlk?cgktq;O{ida<(b><ILP~ z=6cK>=Mq2aWd3m{^ZHI^&MO}m{?<<BpL8-m9Dcn2M>?4!$f(Sz8~Eb*E-4Pm8fTR* zDk@(x-CN)-MIMU%%&OwbDf50%T<FbSR8n5KP&>>bjQnuVDX8+g3caO^i}T7Wd=(*k zV`rjMxkxb!<`+*aUur_mME;k>EO0C<ijhbcQ3Y!P+JC!MSKm~<s+tL1#7)&Vt*-79 z6~&Bh&6+h3J~g#EuX-)Bvy`D}d9$jSuQ931%UerOXG-<jYC<Wdbqf|Oh>3|_T2Zm2 zqT+=4ob1e8#wYtXgYiG9z*|t}EUv69uXL6!a+VeN78NciuDsAWL=<PCX_Gmy{3aEY zmK7H{z2(mGisD7i;-$rfK5xOiGA#hdedZ!Dh<gcibQUaf7F6J|h%h1*he#uJJnjA} ziwlaJC6(n131Ol+bCB%qxX76Y8qEfB2}g%Q=3YLJ{DrE)d7ZDg^7_(6^PMF+V<)&* z6??rMoK=;#h~gFbD#}U=bwvgP+VSIF?(?491MU#%<nHa|Dx_fEVrPYsU^{(Pl8vE5 z%BqG+Qtfn{U+jg9rCw)Yc~P;myd)UHWN0KS#ie|~-zaAPRn*2P>O<C=qx5Q6c7~4s zWi73MVNFe$njrp#3k#?dYD2OD8HSFe{)P%xwV-%j0hVQ@i;Ai9G9>QicS1Z%7L<C6 zt11c#i=71}-r`E<&{4js%2D%57mX@jw0LMRa627z>g{|Yso}A-vV76P;ziz||Mql> zD;Jh7qH4$uRRJ@NxXxD&A*u5Y?DnmhChIdgL}S7)DfKRps;%-CRO;COrD)shOP9x{ z7w3aI(;1TJ{F`>vk*=Dc9sL&->niW)$7Yk6GbW8NJFFf3>y(F{In-HTmqNIuV`x%1 z(f^>Kkglw(e2L~iLU*d}lhpI^HP$JWF48HeobQtgt#YOmFQ#WGEpZkvtnglc_IIZ@ z4_2}}jRG=CyDSbdt1zT<Ve!IpdkgIhHH5Wql{$m@xWSy@o$Xj(Pj^rM)8Jpc`SFOK zT>6vWW<C<T?WdQ}b*jt@=SY8EHRp1e?D{$EFG<H<HK(d{VMST-++e>qr>L~bJI8z8 zxRDh_rsFeYI_Y1T947XTQN4@eRPS*;RPSky#`uOF6>p3&`|B?vF!_RS{RbwBufTgE zGzpsfjfg*Y;0}lC@9nAj7R3`soN|nft?bqm*%;0O-kVRqPtdT~NEJH{2|;DIim)DG zst36X>l3T`jB}_yV-i|>HpMl@HpEm!=Xc9X>=�?dYNUd4WaURX_A__M4W}D0!0n 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/util.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/util.py new file mode 100644 index 0000000..9d4bfd3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/util.py @@ -0,0 +1,1756 @@ +# +# Copyright (C) 2012-2017 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +import codecs +from collections import deque +import contextlib +import csv +from glob import iglob as std_iglob +import io +import json +import logging +import os +import py_compile +import re +import socket +try: + import ssl +except ImportError: # pragma: no cover + ssl = None +import subprocess +import sys +import tarfile +import tempfile +import textwrap + +try: + import threading +except ImportError: # pragma: no cover + import dummy_threading as threading +import time + +from . import DistlibException +from .compat import (string_types, text_type, shutil, raw_input, StringIO, + cache_from_source, urlopen, urljoin, httplib, xmlrpclib, + splittype, HTTPHandler, BaseConfigurator, valid_ident, + Container, configparser, URLError, ZipFile, fsdecode, + unquote, urlparse) + +logger = logging.getLogger(__name__) + +# +# Requirement parsing code as per PEP 508 +# + +IDENTIFIER = re.compile(r'^([\w\.-]+)\s*') +VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*') +COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*') +MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*') +OR = re.compile(r'^or\b\s*') +AND = re.compile(r'^and\b\s*') +NON_SPACE = re.compile(r'(\S+)\s*') +STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)') + + +def parse_marker(marker_string): + """ + Parse a marker string and return a dictionary containing a marker expression. + + The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in + the expression grammar, or strings. A string contained in quotes is to be + interpreted as a literal string, and a string not contained in quotes is a + variable (such as os_name). + """ + def marker_var(remaining): + # either identifier, or literal string + m = IDENTIFIER.match(remaining) + if m: + result = m.groups()[0] + remaining = remaining[m.end():] + elif not remaining: + raise SyntaxError('unexpected end of input') + else: + q = remaining[0] + if q not in '\'"': + raise SyntaxError('invalid expression: %s' % remaining) + oq = '\'"'.replace(q, '') + remaining = remaining[1:] + parts = [q] + while remaining: + # either a string chunk, or oq, or q to terminate + if remaining[0] == q: + break + elif remaining[0] == oq: + parts.append(oq) + remaining = remaining[1:] + else: + m = STRING_CHUNK.match(remaining) + if not m: + raise SyntaxError('error in string literal: %s' % remaining) + parts.append(m.groups()[0]) + remaining = remaining[m.end():] + else: + s = ''.join(parts) + raise SyntaxError('unterminated string: %s' % s) + parts.append(q) + result = ''.join(parts) + remaining = remaining[1:].lstrip() # skip past closing quote + return result, remaining + + def marker_expr(remaining): + if remaining and remaining[0] == '(': + result, remaining = marker(remaining[1:].lstrip()) + if remaining[0] != ')': + raise SyntaxError('unterminated parenthesis: %s' % remaining) + remaining = remaining[1:].lstrip() + else: + lhs, remaining = marker_var(remaining) + while remaining: + m = MARKER_OP.match(remaining) + if not m: + break + op = m.groups()[0] + remaining = remaining[m.end():] + rhs, remaining = marker_var(remaining) + lhs = {'op': op, 'lhs': lhs, 'rhs': rhs} + result = lhs + return result, remaining + + def marker_and(remaining): + lhs, remaining = marker_expr(remaining) + while remaining: + m = AND.match(remaining) + if not m: + break + remaining = remaining[m.end():] + rhs, remaining = marker_expr(remaining) + lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs} + return lhs, remaining + + def marker(remaining): + lhs, remaining = marker_and(remaining) + while remaining: + m = OR.match(remaining) + if not m: + break + remaining = remaining[m.end():] + rhs, remaining = marker_and(remaining) + lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs} + return lhs, remaining + + return marker(marker_string) + + +def parse_requirement(req): + """ + Parse a requirement passed in as a string. Return a Container + whose attributes contain the various parts of the requirement. + """ + remaining = req.strip() + if not remaining or remaining.startswith('#'): + return None + m = IDENTIFIER.match(remaining) + if not m: + raise SyntaxError('name expected: %s' % remaining) + distname = m.groups()[0] + remaining = remaining[m.end():] + extras = mark_expr = versions = uri = None + if remaining and remaining[0] == '[': + i = remaining.find(']', 1) + if i < 0: + raise SyntaxError('unterminated extra: %s' % remaining) + s = remaining[1:i] + remaining = remaining[i + 1:].lstrip() + extras = [] + while s: + m = IDENTIFIER.match(s) + if not m: + raise SyntaxError('malformed extra: %s' % s) + extras.append(m.groups()[0]) + s = s[m.end():] + if not s: + break + if s[0] != ',': + raise SyntaxError('comma expected in extras: %s' % s) + s = s[1:].lstrip() + if not extras: + extras = None + if remaining: + if remaining[0] == '@': + # it's a URI + remaining = remaining[1:].lstrip() + m = NON_SPACE.match(remaining) + if not m: + raise SyntaxError('invalid URI: %s' % remaining) + uri = m.groups()[0] + t = urlparse(uri) + # there are issues with Python and URL parsing, so this test + # is a bit crude. See bpo-20271, bpo-23505. Python doesn't + # always parse invalid URLs correctly - it should raise + # exceptions for malformed URLs + if not (t.scheme and t.netloc): + raise SyntaxError('Invalid URL: %s' % uri) + remaining = remaining[m.end():].lstrip() + else: + + def get_versions(ver_remaining): + """ + Return a list of operator, version tuples if any are + specified, else None. + """ + m = COMPARE_OP.match(ver_remaining) + versions = None + if m: + versions = [] + while True: + op = m.groups()[0] + ver_remaining = ver_remaining[m.end():] + m = VERSION_IDENTIFIER.match(ver_remaining) + if not m: + raise SyntaxError('invalid version: %s' % ver_remaining) + v = m.groups()[0] + versions.append((op, v)) + ver_remaining = ver_remaining[m.end():] + if not ver_remaining or ver_remaining[0] != ',': + break + ver_remaining = ver_remaining[1:].lstrip() + m = COMPARE_OP.match(ver_remaining) + if not m: + raise SyntaxError('invalid constraint: %s' % ver_remaining) + if not versions: + versions = None + return versions, ver_remaining + + if remaining[0] != '(': + versions, remaining = get_versions(remaining) + else: + i = remaining.find(')', 1) + if i < 0: + raise SyntaxError('unterminated parenthesis: %s' % remaining) + s = remaining[1:i] + remaining = remaining[i + 1:].lstrip() + # As a special diversion from PEP 508, allow a version number + # a.b.c in parentheses as a synonym for ~= a.b.c (because this + # is allowed in earlier PEPs) + if COMPARE_OP.match(s): + versions, _ = get_versions(s) + else: + m = VERSION_IDENTIFIER.match(s) + if not m: + raise SyntaxError('invalid constraint: %s' % s) + v = m.groups()[0] + s = s[m.end():].lstrip() + if s: + raise SyntaxError('invalid constraint: %s' % s) + versions = [('~=', v)] + + if remaining: + if remaining[0] != ';': + raise SyntaxError('invalid requirement: %s' % remaining) + remaining = remaining[1:].lstrip() + + mark_expr, remaining = parse_marker(remaining) + + if remaining and remaining[0] != '#': + raise SyntaxError('unexpected trailing data: %s' % remaining) + + if not versions: + rs = distname + else: + rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions])) + return Container(name=distname, extras=extras, constraints=versions, + marker=mark_expr, url=uri, requirement=rs) + + +def get_resources_dests(resources_root, rules): + """Find destinations for resources files""" + + def get_rel_path(root, path): + # normalizes and returns a lstripped-/-separated path + root = root.replace(os.path.sep, '/') + path = path.replace(os.path.sep, '/') + assert path.startswith(root) + return path[len(root):].lstrip('/') + + destinations = {} + for base, suffix, dest in rules: + prefix = os.path.join(resources_root, base) + for abs_base in iglob(prefix): + abs_glob = os.path.join(abs_base, suffix) + for abs_path in iglob(abs_glob): + resource_file = get_rel_path(resources_root, abs_path) + if dest is None: # remove the entry if it was here + destinations.pop(resource_file, None) + else: + rel_path = get_rel_path(abs_base, abs_path) + rel_dest = dest.replace(os.path.sep, '/').rstrip('/') + destinations[resource_file] = rel_dest + '/' + rel_path + return destinations + + +def in_venv(): + if hasattr(sys, 'real_prefix'): + # virtualenv venvs + result = True + else: + # PEP 405 venvs + result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix) + return result + + +def get_executable(): +# The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as +# changes to the stub launcher mean that sys.executable always points +# to the stub on OS X +# if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__' +# in os.environ): +# result = os.environ['__PYVENV_LAUNCHER__'] +# else: +# result = sys.executable +# return result + result = os.path.normcase(sys.executable) + if not isinstance(result, text_type): + result = fsdecode(result) + return result + + +def proceed(prompt, allowed_chars, error_prompt=None, default=None): + p = prompt + while True: + s = raw_input(p) + p = prompt + if not s and default: + s = default + if s: + c = s[0].lower() + if c in allowed_chars: + break + if error_prompt: + p = '%c: %s\n%s' % (c, error_prompt, prompt) + return c + + +def extract_by_key(d, keys): + if isinstance(keys, string_types): + keys = keys.split() + result = {} + for key in keys: + if key in d: + result[key] = d[key] + return result + +def read_exports(stream): + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getreader('utf-8')(stream) + # Try to load as JSON, falling back on legacy format + data = stream.read() + stream = StringIO(data) + try: + jdata = json.load(stream) + result = jdata['extensions']['python.exports']['exports'] + for group, entries in result.items(): + for k, v in entries.items(): + s = '%s = %s' % (k, v) + entry = get_export_entry(s) + assert entry is not None + entries[k] = entry + return result + except Exception: + stream.seek(0, 0) + + def read_stream(cp, stream): + if hasattr(cp, 'read_file'): + cp.read_file(stream) + else: + cp.readfp(stream) + + cp = configparser.ConfigParser() + try: + read_stream(cp, stream) + except configparser.MissingSectionHeaderError: + stream.close() + data = textwrap.dedent(data) + stream = StringIO(data) + read_stream(cp, stream) + + result = {} + for key in cp.sections(): + result[key] = entries = {} + for name, value in cp.items(key): + s = '%s = %s' % (name, value) + entry = get_export_entry(s) + assert entry is not None + #entry.dist = self + entries[name] = entry + return result + + +def write_exports(exports, stream): + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getwriter('utf-8')(stream) + cp = configparser.ConfigParser() + for k, v in exports.items(): + # TODO check k, v for valid values + cp.add_section(k) + for entry in v.values(): + if entry.suffix is None: + s = entry.prefix + else: + s = '%s:%s' % (entry.prefix, entry.suffix) + if entry.flags: + s = '%s [%s]' % (s, ', '.join(entry.flags)) + cp.set(k, entry.name, s) + cp.write(stream) + + +@contextlib.contextmanager +def tempdir(): + td = tempfile.mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + +@contextlib.contextmanager +def chdir(d): + cwd = os.getcwd() + try: + os.chdir(d) + yield + finally: + os.chdir(cwd) + + +@contextlib.contextmanager +def socket_timeout(seconds=15): + cto = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(seconds) + yield + finally: + socket.setdefaulttimeout(cto) + + +class cached_property(object): + def __init__(self, func): + self.func = func + #for attr in ('__name__', '__module__', '__doc__'): + # setattr(self, attr, getattr(func, attr, None)) + + def __get__(self, obj, cls=None): + if obj is None: + return self + value = self.func(obj) + object.__setattr__(obj, self.func.__name__, value) + #obj.__dict__[self.func.__name__] = value = self.func(obj) + return value + +def convert_path(pathname): + """Return 'pathname' as a name that will work on the native filesystem. + + The path is split on '/' and put back together again using the current + directory separator. Needed because filenames in the setup script are + always supplied in Unix style, and have to be converted to the local + convention before we can actually use them in the filesystem. Raises + ValueError on non-Unix-ish systems if 'pathname' either starts or + ends with a slash. + """ + if os.sep == '/': + return pathname + if not pathname: + return pathname + if pathname[0] == '/': + raise ValueError("path '%s' cannot be absolute" % pathname) + if pathname[-1] == '/': + raise ValueError("path '%s' cannot end with '/'" % pathname) + + paths = pathname.split('/') + while os.curdir in paths: + paths.remove(os.curdir) + if not paths: + return os.curdir + return os.path.join(*paths) + + +class FileOperator(object): + def __init__(self, dry_run=False): + self.dry_run = dry_run + self.ensured = set() + self._init_record() + + def _init_record(self): + self.record = False + self.files_written = set() + self.dirs_created = set() + + def record_as_written(self, path): + if self.record: + self.files_written.add(path) + + def newer(self, source, target): + """Tell if the target is newer than the source. + + Returns true if 'source' exists and is more recently modified than + 'target', or if 'source' exists and 'target' doesn't. + + Returns false if both exist and 'target' is the same age or younger + than 'source'. Raise PackagingFileError if 'source' does not exist. + + Note that this test is not very accurate: files created in the same + second will have the same "age". + """ + if not os.path.exists(source): + raise DistlibException("file '%r' does not exist" % + os.path.abspath(source)) + if not os.path.exists(target): + return True + + return os.stat(source).st_mtime > os.stat(target).st_mtime + + def copy_file(self, infile, outfile, check=True): + """Copy a file respecting dry-run and force flags. + """ + self.ensure_dir(os.path.dirname(outfile)) + logger.info('Copying %s to %s', infile, outfile) + if not self.dry_run: + msg = None + if check: + if os.path.islink(outfile): + msg = '%s is a symlink' % outfile + elif os.path.exists(outfile) and not os.path.isfile(outfile): + msg = '%s is a non-regular file' % outfile + if msg: + raise ValueError(msg + ' which would be overwritten') + shutil.copyfile(infile, outfile) + self.record_as_written(outfile) + + def copy_stream(self, instream, outfile, encoding=None): + assert not os.path.isdir(outfile) + self.ensure_dir(os.path.dirname(outfile)) + logger.info('Copying stream %s to %s', instream, outfile) + if not self.dry_run: + if encoding is None: + outstream = open(outfile, 'wb') + else: + outstream = codecs.open(outfile, 'w', encoding=encoding) + try: + shutil.copyfileobj(instream, outstream) + finally: + outstream.close() + self.record_as_written(outfile) + + def write_binary_file(self, path, data): + self.ensure_dir(os.path.dirname(path)) + if not self.dry_run: + if os.path.exists(path): + os.remove(path) + with open(path, 'wb') as f: + f.write(data) + self.record_as_written(path) + + def write_text_file(self, path, data, encoding): + self.write_binary_file(path, data.encode(encoding)) + + def set_mode(self, bits, mask, files): + if os.name == 'posix' or (os.name == 'java' and os._name == 'posix'): + # Set the executable bits (owner, group, and world) on + # all the files specified. + for f in files: + if self.dry_run: + logger.info("changing mode of %s", f) + else: + mode = (os.stat(f).st_mode | bits) & mask + logger.info("changing mode of %s to %o", f, mode) + os.chmod(f, mode) + + set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f) + + def ensure_dir(self, path): + path = os.path.abspath(path) + if path not in self.ensured and not os.path.exists(path): + self.ensured.add(path) + d, f = os.path.split(path) + self.ensure_dir(d) + logger.info('Creating %s' % path) + if not self.dry_run: + os.mkdir(path) + if self.record: + self.dirs_created.add(path) + + def byte_compile(self, path, optimize=False, force=False, prefix=None, hashed_invalidation=False): + dpath = cache_from_source(path, not optimize) + logger.info('Byte-compiling %s to %s', path, dpath) + if not self.dry_run: + if force or self.newer(path, dpath): + if not prefix: + diagpath = None + else: + assert path.startswith(prefix) + diagpath = path[len(prefix):] + compile_kwargs = {} + if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'): + compile_kwargs['invalidation_mode'] = py_compile.PycInvalidationMode.CHECKED_HASH + py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error + self.record_as_written(dpath) + return dpath + + def ensure_removed(self, path): + if os.path.exists(path): + if os.path.isdir(path) and not os.path.islink(path): + logger.debug('Removing directory tree at %s', path) + if not self.dry_run: + shutil.rmtree(path) + if self.record: + if path in self.dirs_created: + self.dirs_created.remove(path) + else: + if os.path.islink(path): + s = 'link' + else: + s = 'file' + logger.debug('Removing %s %s', s, path) + if not self.dry_run: + os.remove(path) + if self.record: + if path in self.files_written: + self.files_written.remove(path) + + def is_writable(self, path): + result = False + while not result: + if os.path.exists(path): + result = os.access(path, os.W_OK) + break + parent = os.path.dirname(path) + if parent == path: + break + path = parent + return result + + def commit(self): + """ + Commit recorded changes, turn off recording, return + changes. + """ + assert self.record + result = self.files_written, self.dirs_created + self._init_record() + return result + + def rollback(self): + if not self.dry_run: + for f in list(self.files_written): + if os.path.exists(f): + os.remove(f) + # dirs should all be empty now, except perhaps for + # __pycache__ subdirs + # reverse so that subdirs appear before their parents + dirs = sorted(self.dirs_created, reverse=True) + for d in dirs: + flist = os.listdir(d) + if flist: + assert flist == ['__pycache__'] + sd = os.path.join(d, flist[0]) + os.rmdir(sd) + os.rmdir(d) # should fail if non-empty + self._init_record() + +def resolve(module_name, dotted_path): + if module_name in sys.modules: + mod = sys.modules[module_name] + else: + mod = __import__(module_name) + if dotted_path is None: + result = mod + else: + parts = dotted_path.split('.') + result = getattr(mod, parts.pop(0)) + for p in parts: + result = getattr(result, p) + return result + + +class ExportEntry(object): + def __init__(self, name, prefix, suffix, flags): + self.name = name + self.prefix = prefix + self.suffix = suffix + self.flags = flags + + @cached_property + def value(self): + return resolve(self.prefix, self.suffix) + + def __repr__(self): # pragma: no cover + return '<ExportEntry %s = %s:%s %s>' % (self.name, self.prefix, + self.suffix, self.flags) + + def __eq__(self, other): + if not isinstance(other, ExportEntry): + result = False + else: + result = (self.name == other.name and + self.prefix == other.prefix and + self.suffix == other.suffix and + self.flags == other.flags) + return result + + __hash__ = object.__hash__ + + +ENTRY_RE = re.compile(r'''(?P<name>(\w|[-.+])+) + \s*=\s*(?P<callable>(\w+)([:\.]\w+)*) + \s*(\[\s*(?P<flags>\w+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? + ''', re.VERBOSE) + +def get_export_entry(specification): + m = ENTRY_RE.search(specification) + if not m: + result = None + if '[' in specification or ']' in specification: + raise DistlibException("Invalid specification " + "'%s'" % specification) + else: + d = m.groupdict() + name = d['name'] + path = d['callable'] + colons = path.count(':') + if colons == 0: + prefix, suffix = path, None + else: + if colons != 1: + raise DistlibException("Invalid specification " + "'%s'" % specification) + prefix, suffix = path.split(':') + flags = d['flags'] + if flags is None: + if '[' in specification or ']' in specification: + raise DistlibException("Invalid specification " + "'%s'" % specification) + flags = [] + else: + flags = [f.strip() for f in flags.split(',')] + result = ExportEntry(name, prefix, suffix, flags) + return result + + +def get_cache_base(suffix=None): + """ + Return the default base location for distlib caches. If the directory does + not exist, it is created. Use the suffix provided for the base directory, + and default to '.distlib' if it isn't provided. + + On Windows, if LOCALAPPDATA is defined in the environment, then it is + assumed to be a directory, and will be the parent directory of the result. + On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home + directory - using os.expanduser('~') - will be the parent directory of + the result. + + The result is just the directory '.distlib' in the parent directory as + determined above, or with the name specified with ``suffix``. + """ + if suffix is None: + suffix = '.distlib' + if os.name == 'nt' and 'LOCALAPPDATA' in os.environ: + result = os.path.expandvars('$localappdata') + else: + # Assume posix, or old Windows + result = os.path.expanduser('~') + # we use 'isdir' instead of 'exists', because we want to + # fail if there's a file with that name + if os.path.isdir(result): + usable = os.access(result, os.W_OK) + if not usable: + logger.warning('Directory exists but is not writable: %s', result) + else: + try: + os.makedirs(result) + usable = True + except OSError: + logger.warning('Unable to create %s', result, exc_info=True) + usable = False + if not usable: + result = tempfile.mkdtemp() + logger.warning('Default location unusable, using %s', result) + return os.path.join(result, suffix) + + +def path_to_cache_dir(path): + """ + Convert an absolute path to a directory name for use in a cache. + + The algorithm used is: + + #. On Windows, any ``':'`` in the drive is replaced with ``'---'``. + #. Any occurrence of ``os.sep`` is replaced with ``'--'``. + #. ``'.cache'`` is appended. + """ + d, p = os.path.splitdrive(os.path.abspath(path)) + if d: + d = d.replace(':', '---') + p = p.replace(os.sep, '--') + return d + p + '.cache' + + +def ensure_slash(s): + if not s.endswith('/'): + return s + '/' + return s + + +def parse_credentials(netloc): + username = password = None + if '@' in netloc: + prefix, netloc = netloc.split('@', 1) + if ':' not in prefix: + username = prefix + else: + username, password = prefix.split(':', 1) + return username, password, netloc + + +def get_process_umask(): + result = os.umask(0o22) + os.umask(result) + return result + +def is_string_sequence(seq): + result = True + i = None + for i, s in enumerate(seq): + if not isinstance(s, string_types): + result = False + break + assert i is not None + return result + +PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-' + '([a-z0-9_.+-]+)', re.I) +PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)') + + +def split_filename(filename, project_name=None): + """ + Extract name, version, python version from a filename (no extension) + + Return name, version, pyver or None + """ + result = None + pyver = None + filename = unquote(filename).replace(' ', '-') + m = PYTHON_VERSION.search(filename) + if m: + pyver = m.group(1) + filename = filename[:m.start()] + if project_name and len(filename) > len(project_name) + 1: + m = re.match(re.escape(project_name) + r'\b', filename) + if m: + n = m.end() + result = filename[:n], filename[n + 1:], pyver + if result is None: + m = PROJECT_NAME_AND_VERSION.match(filename) + if m: + result = m.group(1), m.group(3), pyver + return result + +# Allow spaces in name because of legacy dists like "Twisted Core" +NAME_VERSION_RE = re.compile(r'(?P<name>[\w .-]+)\s*' + r'\(\s*(?P<ver>[^\s)]+)\)$') + +def parse_name_and_version(p): + """ + A utility method used to get name and version from a string. + + From e.g. a Provides-Dist value. + + :param p: A value in a form 'foo (1.0)' + :return: The name and version as a tuple. + """ + m = NAME_VERSION_RE.match(p) + if not m: + raise DistlibException('Ill-formed name/version string: \'%s\'' % p) + d = m.groupdict() + return d['name'].strip().lower(), d['ver'] + +def get_extras(requested, available): + result = set() + requested = set(requested or []) + available = set(available or []) + if '*' in requested: + requested.remove('*') + result |= available + for r in requested: + if r == '-': + result.add(r) + elif r.startswith('-'): + unwanted = r[1:] + if unwanted not in available: + logger.warning('undeclared extra: %s' % unwanted) + if unwanted in result: + result.remove(unwanted) + else: + if r not in available: + logger.warning('undeclared extra: %s' % r) + result.add(r) + return result +# +# Extended metadata functionality +# + +def _get_external_data(url): + result = {} + try: + # urlopen might fail if it runs into redirections, + # because of Python issue #13696. Fixed in locators + # using a custom redirect handler. + resp = urlopen(url) + headers = resp.info() + ct = headers.get('Content-Type') + if not ct.startswith('application/json'): + logger.debug('Unexpected response for JSON request: %s', ct) + else: + reader = codecs.getreader('utf-8')(resp) + #data = reader.read().decode('utf-8') + #result = json.loads(data) + result = json.load(reader) + except Exception as e: + logger.exception('Failed to get external data for %s: %s', url, e) + return result + +_external_data_base_url = 'https://www.red-dove.com/pypi/projects/' + +def get_project_data(name): + url = '%s/%s/project.json' % (name[0].upper(), name) + url = urljoin(_external_data_base_url, url) + result = _get_external_data(url) + return result + +def get_package_data(name, version): + url = '%s/%s/package-%s.json' % (name[0].upper(), name, version) + url = urljoin(_external_data_base_url, url) + return _get_external_data(url) + + +class Cache(object): + """ + A class implementing a cache for resources that need to live in the file system + e.g. shared libraries. This class was moved from resources to here because it + could be used by other modules, e.g. the wheel module. + """ + + def __init__(self, base): + """ + Initialise an instance. + + :param base: The base directory where the cache should be located. + """ + # we use 'isdir' instead of 'exists', because we want to + # fail if there's a file with that name + if not os.path.isdir(base): # pragma: no cover + os.makedirs(base) + if (os.stat(base).st_mode & 0o77) != 0: + logger.warning('Directory \'%s\' is not private', base) + self.base = os.path.abspath(os.path.normpath(base)) + + def prefix_to_dir(self, prefix): + """ + Converts a resource prefix to a directory name in the cache. + """ + return path_to_cache_dir(prefix) + + def clear(self): + """ + Clear the cache. + """ + not_removed = [] + for fn in os.listdir(self.base): + fn = os.path.join(self.base, fn) + try: + if os.path.islink(fn) or os.path.isfile(fn): + os.remove(fn) + elif os.path.isdir(fn): + shutil.rmtree(fn) + except Exception: + not_removed.append(fn) + return not_removed + + +class EventMixin(object): + """ + A very simple publish/subscribe system. + """ + def __init__(self): + self._subscribers = {} + + def add(self, event, subscriber, append=True): + """ + Add a subscriber for an event. + + :param event: The name of an event. + :param subscriber: The subscriber to be added (and called when the + event is published). + :param append: Whether to append or prepend the subscriber to an + existing subscriber list for the event. + """ + subs = self._subscribers + if event not in subs: + subs[event] = deque([subscriber]) + else: + sq = subs[event] + if append: + sq.append(subscriber) + else: + sq.appendleft(subscriber) + + def remove(self, event, subscriber): + """ + Remove a subscriber for an event. + + :param event: The name of an event. + :param subscriber: The subscriber to be removed. + """ + subs = self._subscribers + if event not in subs: + raise ValueError('No subscribers: %r' % event) + subs[event].remove(subscriber) + + def get_subscribers(self, event): + """ + Return an iterator for the subscribers for an event. + :param event: The event to return subscribers for. + """ + return iter(self._subscribers.get(event, ())) + + def publish(self, event, *args, **kwargs): + """ + Publish a event and return a list of values returned by its + subscribers. + + :param event: The event to publish. + :param args: The positional arguments to pass to the event's + subscribers. + :param kwargs: The keyword arguments to pass to the event's + subscribers. + """ + result = [] + for subscriber in self.get_subscribers(event): + try: + value = subscriber(event, *args, **kwargs) + except Exception: + logger.exception('Exception during event publication') + value = None + result.append(value) + logger.debug('publish %s: args = %s, kwargs = %s, result = %s', + event, args, kwargs, result) + return result + +# +# Simple sequencing +# +class Sequencer(object): + def __init__(self): + self._preds = {} + self._succs = {} + self._nodes = set() # nodes with no preds/succs + + def add_node(self, node): + self._nodes.add(node) + + def remove_node(self, node, edges=False): + if node in self._nodes: + self._nodes.remove(node) + if edges: + for p in set(self._preds.get(node, ())): + self.remove(p, node) + for s in set(self._succs.get(node, ())): + self.remove(node, s) + # Remove empties + for k, v in list(self._preds.items()): + if not v: + del self._preds[k] + for k, v in list(self._succs.items()): + if not v: + del self._succs[k] + + def add(self, pred, succ): + assert pred != succ + self._preds.setdefault(succ, set()).add(pred) + self._succs.setdefault(pred, set()).add(succ) + + def remove(self, pred, succ): + assert pred != succ + try: + preds = self._preds[succ] + succs = self._succs[pred] + except KeyError: # pragma: no cover + raise ValueError('%r not a successor of anything' % succ) + try: + preds.remove(pred) + succs.remove(succ) + except KeyError: # pragma: no cover + raise ValueError('%r not a successor of %r' % (succ, pred)) + + def is_step(self, step): + return (step in self._preds or step in self._succs or + step in self._nodes) + + def get_steps(self, final): + if not self.is_step(final): + raise ValueError('Unknown: %r' % final) + result = [] + todo = [] + seen = set() + todo.append(final) + while todo: + step = todo.pop(0) + if step in seen: + # if a step was already seen, + # move it to the end (so it will appear earlier + # when reversed on return) ... but not for the + # final step, as that would be confusing for + # users + if step != final: + result.remove(step) + result.append(step) + else: + seen.add(step) + result.append(step) + preds = self._preds.get(step, ()) + todo.extend(preds) + return reversed(result) + + @property + def strong_connections(self): + #http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + index_counter = [0] + stack = [] + lowlinks = {} + index = {} + result = [] + + graph = self._succs + + def strongconnect(node): + # set the depth index for this node to the smallest unused index + index[node] = index_counter[0] + lowlinks[node] = index_counter[0] + index_counter[0] += 1 + stack.append(node) + + # Consider successors + try: + successors = graph[node] + except Exception: + successors = [] + for successor in successors: + if successor not in lowlinks: + # Successor has not yet been visited + strongconnect(successor) + lowlinks[node] = min(lowlinks[node],lowlinks[successor]) + elif successor in stack: + # the successor is in the stack and hence in the current + # strongly connected component (SCC) + lowlinks[node] = min(lowlinks[node],index[successor]) + + # If `node` is a root node, pop the stack and generate an SCC + if lowlinks[node] == index[node]: + connected_component = [] + + while True: + successor = stack.pop() + connected_component.append(successor) + if successor == node: break + component = tuple(connected_component) + # storing the result + result.append(component) + + for node in graph: + if node not in lowlinks: + strongconnect(node) + + return result + + @property + def dot(self): + result = ['digraph G {'] + for succ in self._preds: + preds = self._preds[succ] + for pred in preds: + result.append(' %s -> %s;' % (pred, succ)) + for node in self._nodes: + result.append(' %s;' % node) + result.append('}') + return '\n'.join(result) + +# +# Unarchiving functionality for zip, tar, tgz, tbz, whl +# + +ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', + '.tgz', '.tbz', '.whl') + +def unarchive(archive_filename, dest_dir, format=None, check=True): + + def check_path(path): + if not isinstance(path, text_type): + path = path.decode('utf-8') + p = os.path.abspath(os.path.join(dest_dir, path)) + if not p.startswith(dest_dir) or p[plen] != os.sep: + raise ValueError('path outside destination: %r' % p) + + dest_dir = os.path.abspath(dest_dir) + plen = len(dest_dir) + archive = None + if format is None: + if archive_filename.endswith(('.zip', '.whl')): + format = 'zip' + elif archive_filename.endswith(('.tar.gz', '.tgz')): + format = 'tgz' + mode = 'r:gz' + elif archive_filename.endswith(('.tar.bz2', '.tbz')): + format = 'tbz' + mode = 'r:bz2' + elif archive_filename.endswith('.tar'): + format = 'tar' + mode = 'r' + else: # pragma: no cover + raise ValueError('Unknown format for %r' % archive_filename) + try: + if format == 'zip': + archive = ZipFile(archive_filename, 'r') + if check: + names = archive.namelist() + for name in names: + check_path(name) + else: + archive = tarfile.open(archive_filename, mode) + if check: + names = archive.getnames() + for name in names: + check_path(name) + if format != 'zip' and sys.version_info[0] < 3: + # See Python issue 17153. If the dest path contains Unicode, + # tarfile extraction fails on Python 2.x if a member path name + # contains non-ASCII characters - it leads to an implicit + # bytes -> unicode conversion using ASCII to decode. + for tarinfo in archive.getmembers(): + if not isinstance(tarinfo.name, text_type): + tarinfo.name = tarinfo.name.decode('utf-8') + archive.extractall(dest_dir) + + finally: + if archive: + archive.close() + + +def zip_dir(directory): + """zip a directory tree into a BytesIO object""" + result = io.BytesIO() + dlen = len(directory) + with ZipFile(result, "w") as zf: + for root, dirs, files in os.walk(directory): + for name in files: + full = os.path.join(root, name) + rel = root[dlen:] + dest = os.path.join(rel, name) + zf.write(full, dest) + return result + +# +# Simple progress bar +# + +UNITS = ('', 'K', 'M', 'G','T','P') + + +class Progress(object): + unknown = 'UNKNOWN' + + def __init__(self, minval=0, maxval=100): + assert maxval is None or maxval >= minval + self.min = self.cur = minval + self.max = maxval + self.started = None + self.elapsed = 0 + self.done = False + + def update(self, curval): + assert self.min <= curval + assert self.max is None or curval <= self.max + self.cur = curval + now = time.time() + if self.started is None: + self.started = now + else: + self.elapsed = now - self.started + + def increment(self, incr): + assert incr >= 0 + self.update(self.cur + incr) + + def start(self): + self.update(self.min) + return self + + def stop(self): + if self.max is not None: + self.update(self.max) + self.done = True + + @property + def maximum(self): + return self.unknown if self.max is None else self.max + + @property + def percentage(self): + if self.done: + result = '100 %' + elif self.max is None: + result = ' ?? %' + else: + v = 100.0 * (self.cur - self.min) / (self.max - self.min) + result = '%3d %%' % v + return result + + def format_duration(self, duration): + if (duration <= 0) and self.max is None or self.cur == self.min: + result = '??:??:??' + #elif duration < 1: + # result = '--:--:--' + else: + result = time.strftime('%H:%M:%S', time.gmtime(duration)) + return result + + @property + def ETA(self): + if self.done: + prefix = 'Done' + t = self.elapsed + #import pdb; pdb.set_trace() + else: + prefix = 'ETA ' + if self.max is None: + t = -1 + elif self.elapsed == 0 or (self.cur == self.min): + t = 0 + else: + #import pdb; pdb.set_trace() + t = float(self.max - self.min) + t /= self.cur - self.min + t = (t - 1) * self.elapsed + return '%s: %s' % (prefix, self.format_duration(t)) + + @property + def speed(self): + if self.elapsed == 0: + result = 0.0 + else: + result = (self.cur - self.min) / self.elapsed + for unit in UNITS: + if result < 1000: + break + result /= 1000.0 + return '%d %sB/s' % (result, unit) + +# +# Glob functionality +# + +RICH_GLOB = re.compile(r'\{([^}]*)\}') +_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]') +_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$') + + +def iglob(path_glob): + """Extended globbing function that supports ** and {opt1,opt2,opt3}.""" + if _CHECK_RECURSIVE_GLOB.search(path_glob): + msg = """invalid glob %r: recursive glob "**" must be used alone""" + raise ValueError(msg % path_glob) + if _CHECK_MISMATCH_SET.search(path_glob): + msg = """invalid glob %r: mismatching set marker '{' or '}'""" + raise ValueError(msg % path_glob) + return _iglob(path_glob) + + +def _iglob(path_glob): + rich_path_glob = RICH_GLOB.split(path_glob, 1) + if len(rich_path_glob) > 1: + assert len(rich_path_glob) == 3, rich_path_glob + prefix, set, suffix = rich_path_glob + for item in set.split(','): + for path in _iglob(''.join((prefix, item, suffix))): + yield path + else: + if '**' not in path_glob: + for item in std_iglob(path_glob): + yield item + else: + prefix, radical = path_glob.split('**', 1) + if prefix == '': + prefix = '.' + if radical == '': + radical = '*' + else: + # we support both + radical = radical.lstrip('/') + radical = radical.lstrip('\\') + for path, dir, files in os.walk(prefix): + path = os.path.normpath(path) + for fn in _iglob(os.path.join(path, radical)): + yield fn + +if ssl: + from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname, + CertificateError) + + +# +# HTTPSConnection which verifies certificates/matches domains +# + + class HTTPSConnection(httplib.HTTPSConnection): + ca_certs = None # set this to the path to the certs file (.pem) + check_domain = True # only used if ca_certs is not None + + # noinspection PyPropertyAccess + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if getattr(self, '_tunnel_host', False): + 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) + 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) + logger.debug('Host verified: %s', self.host) + except CertificateError: # pragma: no cover + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + + class HTTPSHandler(BaseHTTPSHandler): + def __init__(self, ca_certs, check_domain=True): + BaseHTTPSHandler.__init__(self) + self.ca_certs = ca_certs + self.check_domain = check_domain + + def _conn_maker(self, *args, **kwargs): + """ + This is called to create a connection instance. Normally you'd + pass a connection class to do_open, but it doesn't actually check for + a class, and just expects a callable. As long as we behave just as a + constructor would have, we should be OK. If it ever changes so that + we *must* pass a class, we'll create an UnsafeHTTPSConnection class + which just sets check_domain to False in the class definition, and + choose which one to pass to do_open. + """ + result = HTTPSConnection(*args, **kwargs) + if self.ca_certs: + result.ca_certs = self.ca_certs + result.check_domain = self.check_domain + return result + + def https_open(self, req): + try: + return self.do_open(self._conn_maker, req) + except URLError as e: + if 'certificate verify failed' in str(e.reason): + raise CertificateError('Unable to verify server certificate ' + 'for %s' % req.host) + else: + raise + + # + # To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The- + # Middle proxy using HTTP listens on port 443, or an index mistakenly serves + # HTML containing a http://xyz link when it should be https://xyz), + # you can use the following handler class, which does not allow HTTP traffic. + # + # It works by inheriting from HTTPHandler - so build_opener won't add a + # handler for HTTP itself. + # + class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler): + def http_open(self, req): + raise URLError('Unexpected HTTP request on what should be a secure ' + 'connection: %s' % req) + +# +# 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 + xmlrpclib.Transport.__init__(self, use_datetime) + + 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 ssl: + class SafeTransport(xmlrpclib.SafeTransport): + def __init__(self, timeout, use_datetime=0): + self.timeout = timeout + xmlrpclib.SafeTransport.__init__(self, use_datetime) + + def make_connection(self, host): + h, eh, kwargs = self.get_host_info(host) + 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 + + +class ServerProxy(xmlrpclib.ServerProxy): + def __init__(self, uri, **kwargs): + self.timeout = timeout = kwargs.pop('timeout', None) + # The above classes only come into play if a timeout + # is specified + if timeout is not None: + scheme, _ = splittype(uri) + use_datetime = kwargs.get('use_datetime', 0) + if scheme == 'https': + tcls = SafeTransport + else: + tcls = Transport + kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime) + self.transport = t + xmlrpclib.ServerProxy.__init__(self, uri, **kwargs) + +# +# CSV functionality. This is provided because on 2.x, the csv module can't +# handle Unicode. However, we need to deal with Unicode in e.g. RECORD files. +# + +def _csv_open(fn, mode, **kwargs): + if sys.version_info[0] < 3: + mode += 'b' + else: + kwargs['newline'] = '' + # Python 3 determines encoding from locale. Force 'utf-8' + # file encoding to match other forced utf-8 encoding + kwargs['encoding'] = 'utf-8' + return open(fn, mode, **kwargs) + + +class CSVBase(object): + defaults = { + 'delimiter': str(','), # The strs are used because we need native + 'quotechar': str('"'), # str in the csv API (2.x won't take + 'lineterminator': str('\n') # Unicode) + } + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.stream.close() + + +class CSVReader(CSVBase): + def __init__(self, **kwargs): + if 'stream' in kwargs: + stream = kwargs['stream'] + if sys.version_info[0] >= 3: + # needs to be a text stream + stream = codecs.getreader('utf-8')(stream) + self.stream = stream + else: + self.stream = _csv_open(kwargs['path'], 'r') + self.reader = csv.reader(self.stream, **self.defaults) + + def __iter__(self): + return self + + def next(self): + result = next(self.reader) + if sys.version_info[0] < 3: + for i, item in enumerate(result): + if not isinstance(item, text_type): + result[i] = item.decode('utf-8') + return result + + __next__ = next + +class CSVWriter(CSVBase): + def __init__(self, fn, **kwargs): + self.stream = _csv_open(fn, 'w') + self.writer = csv.writer(self.stream, **self.defaults) + + def writerow(self, row): + if sys.version_info[0] < 3: + r = [] + for item in row: + if isinstance(item, text_type): + item = item.encode('utf-8') + r.append(item) + row = r + self.writer.writerow(row) + +# +# Configurator functionality +# + +class Configurator(BaseConfigurator): + + value_converters = dict(BaseConfigurator.value_converters) + value_converters['inc'] = 'inc_convert' + + def __init__(self, config, base=None): + super(Configurator, self).__init__(config) + self.base = base or os.getcwd() + + def configure_custom(self, config): + def convert(o): + if isinstance(o, (list, tuple)): + result = type(o)([convert(i) for i in o]) + elif isinstance(o, dict): + if '()' in o: + result = self.configure_custom(o) + else: + result = {} + for k in o: + result[k] = convert(o[k]) + else: + result = self.convert(o) + return result + + c = config.pop('()') + if not callable(c): + c = self.resolve(c) + props = config.pop('.', None) + # Check for valid identifiers + args = config.pop('[]', ()) + if args: + args = tuple([convert(o) for o in args]) + items = [(k, convert(config[k])) for k in config if valid_ident(k)] + kwargs = dict(items) + result = c(*args, **kwargs) + if props: + for n, v in props.items(): + setattr(result, n, convert(v)) + return result + + def __getitem__(self, key): + result = self.config[key] + if isinstance(result, dict) and '()' in result: + self.config[key] = result = self.configure_custom(result) + return result + + def inc_convert(self, value): + """Default converter for the inc:// protocol.""" + if not os.path.isabs(value): + value = os.path.join(self.base, value) + with codecs.open(value, 'r', encoding='utf-8') as f: + result = json.load(f) + return result + + +class SubprocessMixin(object): + """ + Mixin for running subprocesses and capturing their output + """ + def __init__(self, verbose=False, progress=None): + self.verbose = verbose + self.progress = progress + + def reader(self, stream, context): + """ + Read lines from a subprocess' output stream and either pass to a progress + callable (if specified) or write progress information to sys.stderr. + """ + progress = self.progress + verbose = self.verbose + while True: + s = stream.readline() + if not s: + break + if progress is not None: + progress(s, context) + else: + if not verbose: + sys.stderr.write('.') + else: + sys.stderr.write(s.decode('utf-8')) + sys.stderr.flush() + stream.close() + + def run_command(self, cmd, **kwargs): + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, **kwargs) + t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout')) + t1.start() + t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr')) + t2.start() + p.wait() + t1.join() + t2.join() + if self.progress is not None: + self.progress('done.', 'main') + elif self.verbose: + sys.stderr.write('done.\n') + return p + + +def normalize_name(name): + """Normalize a python package name a la PEP 503""" + # https://www.python.org/dev/peps/pep-0503/#normalized-names + return re.sub('[-_.]+', '-', name).lower() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/version.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/version.py new file mode 100644 index 0000000..3eebe18 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/version.py @@ -0,0 +1,736 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012-2017 The Python Software Foundation. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +""" +Implementation of a flexible versioning scheme providing support for PEP-440, +setuptools-compatible and semantic versioning. +""" + +import logging +import re + +from .compat import string_types +from .util import parse_requirement + +__all__ = ['NormalizedVersion', 'NormalizedMatcher', + 'LegacyVersion', 'LegacyMatcher', + 'SemanticVersion', 'SemanticMatcher', + 'UnsupportedVersionError', 'get_scheme'] + +logger = logging.getLogger(__name__) + + +class UnsupportedVersionError(ValueError): + """This is an unsupported version.""" + pass + + +class Version(object): + def __init__(self, s): + self._string = s = s.strip() + self._parts = parts = self.parse(s) + assert isinstance(parts, tuple) + assert len(parts) > 0 + + def parse(self, s): + raise NotImplementedError('please implement in a subclass') + + def _check_compatible(self, other): + if type(self) != type(other): + raise TypeError('cannot compare %r and %r' % (self, other)) + + def __eq__(self, other): + self._check_compatible(other) + return self._parts == other._parts + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + self._check_compatible(other) + return self._parts < other._parts + + def __gt__(self, other): + return not (self.__lt__(other) or self.__eq__(other)) + + def __le__(self, other): + return self.__lt__(other) or self.__eq__(other) + + def __ge__(self, other): + return self.__gt__(other) or self.__eq__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + def __hash__(self): + return hash(self._parts) + + def __repr__(self): + return "%s('%s')" % (self.__class__.__name__, self._string) + + def __str__(self): + return self._string + + @property + def is_prerelease(self): + raise NotImplementedError('Please implement in subclasses.') + + +class Matcher(object): + version_class = None + + # value is either a callable or the name of a method + _operators = { + '<': lambda v, c, p: v < c, + '>': lambda v, c, p: v > c, + '<=': lambda v, c, p: v == c or v < c, + '>=': lambda v, c, p: v == c or v > c, + '==': lambda v, c, p: v == c, + '===': lambda v, c, p: v == c, + # by default, compatible => >=. + '~=': lambda v, c, p: v == c or v > c, + '!=': lambda v, c, p: v != c, + } + + # this is a method only to support alternative implementations + # via overriding + def parse_requirement(self, s): + return parse_requirement(s) + + def __init__(self, s): + if self.version_class is None: + raise ValueError('Please specify a version class') + self._string = s = s.strip() + r = self.parse_requirement(s) + if not r: + raise ValueError('Not valid: %r' % s) + self.name = r.name + self.key = self.name.lower() # for case-insensitive comparisons + clist = [] + if r.constraints: + # import pdb; pdb.set_trace() + for op, s in r.constraints: + if s.endswith('.*'): + if op not in ('==', '!='): + raise ValueError('\'.*\' not allowed for ' + '%r constraints' % op) + # Could be a partial version (e.g. for '2.*') which + # won't parse as a version, so keep it as a string + vn, prefix = s[:-2], True + # Just to check that vn is a valid version + self.version_class(vn) + else: + # Should parse as a version, so we can create an + # instance for the comparison + vn, prefix = self.version_class(s), False + clist.append((op, vn, prefix)) + self._parts = tuple(clist) + + def match(self, version): + """ + Check if the provided version matches the constraints. + + :param version: The version to match against this instance. + :type version: String or :class:`Version` instance. + """ + if isinstance(version, string_types): + version = self.version_class(version) + for operator, constraint, prefix in self._parts: + f = self._operators.get(operator) + if isinstance(f, string_types): + f = getattr(self, f) + if not f: + msg = ('%r not implemented ' + 'for %s' % (operator, self.__class__.__name__)) + raise NotImplementedError(msg) + if not f(version, constraint, prefix): + return False + return True + + @property + def exact_version(self): + result = None + if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='): + result = self._parts[0][1] + return result + + def _check_compatible(self, other): + if type(self) != type(other) or self.name != other.name: + raise TypeError('cannot compare %s and %s' % (self, other)) + + def __eq__(self, other): + self._check_compatible(other) + return self.key == other.key and self._parts == other._parts + + def __ne__(self, other): + return not self.__eq__(other) + + # See http://docs.python.org/reference/datamodel#object.__hash__ + def __hash__(self): + return hash(self.key) + hash(self._parts) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self._string) + + def __str__(self): + return self._string + + +PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|b|c|rc)(\d+))?' + r'(\.(post)(\d+))?(\.(dev)(\d+))?' + r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$') + + +def _pep_440_key(s): + s = s.strip() + m = PEP440_VERSION_RE.match(s) + if not m: + raise UnsupportedVersionError('Not a valid version: %s' % s) + groups = m.groups() + nums = tuple(int(v) for v in groups[1].split('.')) + while len(nums) > 1 and nums[-1] == 0: + nums = nums[:-1] + + if not groups[0]: + epoch = 0 + else: + epoch = int(groups[0]) + pre = groups[4:6] + post = groups[7:9] + dev = groups[10:12] + local = groups[13] + if pre == (None, None): + pre = () + else: + pre = pre[0], int(pre[1]) + if post == (None, None): + post = () + else: + post = post[0], int(post[1]) + if dev == (None, None): + dev = () + else: + dev = dev[0], int(dev[1]) + if local is None: + local = () + else: + parts = [] + for part in local.split('.'): + # to ensure that numeric compares as > lexicographic, avoid + # comparing them directly, but encode a tuple which ensures + # correct sorting + if part.isdigit(): + part = (1, int(part)) + else: + part = (0, part) + parts.append(part) + local = tuple(parts) + if not pre: + # either before pre-release, or final release and after + if not post and dev: + # before pre-release + pre = ('a', -1) # to sort before a0 + else: + pre = ('z',) # to sort after all pre-releases + # now look at the state of post and dev. + if not post: + post = ('_',) # sort before 'a' + if not dev: + dev = ('final',) + + #print('%s -> %s' % (s, m.groups())) + return epoch, nums, pre, post, dev, local + + +_normalized_key = _pep_440_key + + +class NormalizedVersion(Version): + """A rational version. + + Good: + 1.2 # equivalent to "1.2.0" + 1.2.0 + 1.2a1 + 1.2.3a2 + 1.2.3b1 + 1.2.3c1 + 1.2.3.4 + TODO: fill this out + + Bad: + 1 # minimum two numbers + 1.2a # release level must have a release serial + 1.2.3b + """ + def parse(self, s): + result = _normalized_key(s) + # _normalized_key loses trailing zeroes in the release + # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0 + # However, PEP 440 prefix matching needs it: for example, + # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0). + m = PEP440_VERSION_RE.match(s) # must succeed + groups = m.groups() + self._release_clause = tuple(int(v) for v in groups[1].split('.')) + return result + + PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev']) + + @property + def is_prerelease(self): + return any(t[0] in self.PREREL_TAGS for t in self._parts if t) + + +def _match_prefix(x, y): + x = str(x) + y = str(y) + if x == y: + return True + if not x.startswith(y): + return False + n = len(y) + return x[n] == '.' + + +class NormalizedMatcher(Matcher): + version_class = NormalizedVersion + + # value is either a callable or the name of a method + _operators = { + '~=': '_match_compatible', + '<': '_match_lt', + '>': '_match_gt', + '<=': '_match_le', + '>=': '_match_ge', + '==': '_match_eq', + '===': '_match_arbitrary', + '!=': '_match_ne', + } + + def _adjust_local(self, version, constraint, prefix): + if prefix: + strip_local = '+' not in constraint and version._parts[-1] + else: + # both constraint and version are + # NormalizedVersion instances. + # If constraint does not have a local component, + # ensure the version doesn't, either. + strip_local = not constraint._parts[-1] and version._parts[-1] + if strip_local: + s = version._string.split('+', 1)[0] + version = self.version_class(s) + return version, constraint + + def _match_lt(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version >= constraint: + return False + release_clause = constraint._release_clause + pfx = '.'.join([str(i) for i in release_clause]) + return not _match_prefix(version, pfx) + + def _match_gt(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version <= constraint: + return False + release_clause = constraint._release_clause + pfx = '.'.join([str(i) for i in release_clause]) + return not _match_prefix(version, pfx) + + def _match_le(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + return version <= constraint + + def _match_ge(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + return version >= constraint + + def _match_eq(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if not prefix: + result = (version == constraint) + else: + result = _match_prefix(version, constraint) + return result + + def _match_arbitrary(self, version, constraint, prefix): + return str(version) == str(constraint) + + def _match_ne(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if not prefix: + result = (version != constraint) + else: + result = not _match_prefix(version, constraint) + return result + + def _match_compatible(self, version, constraint, prefix): + version, constraint = self._adjust_local(version, constraint, prefix) + if version == constraint: + return True + if version < constraint: + return False +# if not prefix: +# return True + release_clause = constraint._release_clause + if len(release_clause) > 1: + release_clause = release_clause[:-1] + pfx = '.'.join([str(i) for i in release_clause]) + return _match_prefix(version, pfx) + +_REPLACEMENTS = ( + (re.compile('[.+-]$'), ''), # remove trailing puncts + (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start + (re.compile('^[.-]'), ''), # remove leading puncts + (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses + (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion) + (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion) + (re.compile('[.]{2,}'), '.'), # multiple runs of '.' + (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha + (re.compile(r'\b(pre-alpha|prealpha)\b'), + 'pre.alpha'), # standardise + (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses +) + +_SUFFIX_REPLACEMENTS = ( + (re.compile('^[:~._+-]+'), ''), # remove leading puncts + (re.compile('[,*")([\\]]'), ''), # remove unwanted chars + (re.compile('[~:+_ -]'), '.'), # replace illegal chars + (re.compile('[.]{2,}'), '.'), # multiple runs of '.' + (re.compile(r'\.$'), ''), # trailing '.' +) + +_NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)') + + +def _suggest_semantic_version(s): + """ + Try to suggest a semantic form for a version for which + _suggest_normalized_version couldn't come up with anything. + """ + result = s.strip().lower() + for pat, repl in _REPLACEMENTS: + result = pat.sub(repl, result) + if not result: + result = '0.0.0' + + # Now look for numeric prefix, and separate it out from + # the rest. + #import pdb; pdb.set_trace() + m = _NUMERIC_PREFIX.match(result) + if not m: + prefix = '0.0.0' + suffix = result + else: + prefix = m.groups()[0].split('.') + prefix = [int(i) for i in prefix] + while len(prefix) < 3: + prefix.append(0) + if len(prefix) == 3: + suffix = result[m.end():] + else: + suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():] + prefix = prefix[:3] + prefix = '.'.join([str(i) for i in prefix]) + suffix = suffix.strip() + if suffix: + #import pdb; pdb.set_trace() + # massage the suffix. + for pat, repl in _SUFFIX_REPLACEMENTS: + suffix = pat.sub(repl, suffix) + + if not suffix: + result = prefix + else: + sep = '-' if 'dev' in suffix else '+' + result = prefix + sep + suffix + if not is_semver(result): + result = None + return result + + +def _suggest_normalized_version(s): + """Suggest a normalized version close to the given version string. + + If you have a version string that isn't rational (i.e. NormalizedVersion + doesn't like it) then you might be able to get an equivalent (or close) + rational version from this function. + + This does a number of simple normalizations to the given string, based + on observation of versions currently in use on PyPI. Given a dump of + those version during PyCon 2009, 4287 of them: + - 2312 (53.93%) match NormalizedVersion without change + with the automatic suggestion + - 3474 (81.04%) match when using this suggestion method + + @param s {str} An irrational version string. + @returns A rational version string, or None, if couldn't determine one. + """ + try: + _normalized_key(s) + return s # already rational + except UnsupportedVersionError: + pass + + rs = s.lower() + + # part of this could use maketrans + for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), + ('beta', 'b'), ('rc', 'c'), ('-final', ''), + ('-pre', 'c'), + ('-release', ''), ('.release', ''), ('-stable', ''), + ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), + ('final', '')): + rs = rs.replace(orig, repl) + + # if something ends with dev or pre, we add a 0 + rs = re.sub(r"pre$", r"pre0", rs) + rs = re.sub(r"dev$", r"dev0", rs) + + # if we have something like "b-2" or "a.2" at the end of the + # version, that is probably beta, alpha, etc + # let's remove the dash or dot + rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs) + + # 1.0-dev-r371 -> 1.0.dev371 + # 0.1-dev-r79 -> 0.1.dev79 + rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) + + # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 + rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) + + # Clean: v0.3, v1.0 + if rs.startswith('v'): + rs = rs[1:] + + # Clean leading '0's on numbers. + #TODO: unintended side-effect on, e.g., "2003.05.09" + # PyPI stats: 77 (~2%) better + rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) + + # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers + # zero. + # PyPI stats: 245 (7.56%) better + rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) + + # the 'dev-rNNN' tag is a dev tag + rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) + + # clean the - when used as a pre delimiter + rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) + + # a terminal "dev" or "devel" can be changed into ".dev0" + rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) + + # a terminal "dev" can be changed into ".dev0" + rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) + + # a terminal "final" or "stable" can be removed + rs = re.sub(r"(final|stable)$", "", rs) + + # The 'r' and the '-' tags are post release tags + # 0.4a1.r10 -> 0.4a1.post10 + # 0.9.33-17222 -> 0.9.33.post17222 + # 0.9.33-r17222 -> 0.9.33.post17222 + rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) + + # Clean 'r' instead of 'dev' usage: + # 0.9.33+r17222 -> 0.9.33.dev17222 + # 1.0dev123 -> 1.0.dev123 + # 1.0.git123 -> 1.0.dev123 + # 1.0.bzr123 -> 1.0.dev123 + # 0.1a0dev.123 -> 0.1a0.dev123 + # PyPI stats: ~150 (~4%) better + rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) + + # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: + # 0.2.pre1 -> 0.2c1 + # 0.2-c1 -> 0.2c1 + # 1.0preview123 -> 1.0c123 + # PyPI stats: ~21 (0.62%) better + rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) + + # Tcl/Tk uses "px" for their post release markers + rs = re.sub(r"p(\d+)$", r".post\1", rs) + + try: + _normalized_key(rs) + except UnsupportedVersionError: + rs = None + return rs + +# +# Legacy version processing (distribute-compatible) +# + +_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I) +_VERSION_REPLACE = { + 'pre': 'c', + 'preview': 'c', + '-': 'final-', + 'rc': 'c', + 'dev': '@', + '': None, + '.': None, +} + + +def _legacy_key(s): + def get_parts(s): + result = [] + for p in _VERSION_PART.split(s.lower()): + p = _VERSION_REPLACE.get(p, p) + if p: + if '0' <= p[:1] <= '9': + p = p.zfill(8) + else: + p = '*' + p + result.append(p) + result.append('*final') + return result + + result = [] + for p in get_parts(s): + if p.startswith('*'): + if p < '*final': + while result and result[-1] == '*final-': + result.pop() + while result and result[-1] == '00000000': + result.pop() + result.append(p) + return tuple(result) + + +class LegacyVersion(Version): + def parse(self, s): + return _legacy_key(s) + + @property + def is_prerelease(self): + result = False + for x in self._parts: + if (isinstance(x, string_types) and x.startswith('*') and + x < '*final'): + result = True + break + return result + + +class LegacyMatcher(Matcher): + version_class = LegacyVersion + + _operators = dict(Matcher._operators) + _operators['~='] = '_match_compatible' + + numeric_re = re.compile(r'^(\d+(\.\d+)*)') + + def _match_compatible(self, version, constraint, prefix): + if version < constraint: + return False + m = self.numeric_re.match(str(constraint)) + if not m: + logger.warning('Cannot compute compatible match for version %s ' + ' and constraint %s', version, constraint) + return True + s = m.groups()[0] + if '.' in s: + s = s.rsplit('.', 1)[0] + return _match_prefix(version, s) + +# +# Semantic versioning +# + +_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)' + r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?' + r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I) + + +def is_semver(s): + return _SEMVER_RE.match(s) + + +def _semantic_key(s): + def make_tuple(s, absent): + if s is None: + result = (absent,) + else: + parts = s[1:].split('.') + # We can't compare ints and strings on Python 3, so fudge it + # by zero-filling numeric values so simulate a numeric comparison + result = tuple([p.zfill(8) if p.isdigit() else p for p in parts]) + return result + + m = is_semver(s) + if not m: + raise UnsupportedVersionError(s) + groups = m.groups() + major, minor, patch = [int(i) for i in groups[:3]] + # choose the '|' and '*' so that versions sort correctly + pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*') + return (major, minor, patch), pre, build + + +class SemanticVersion(Version): + def parse(self, s): + return _semantic_key(s) + + @property + def is_prerelease(self): + return self._parts[1][0] != '|' + + +class SemanticMatcher(Matcher): + version_class = SemanticVersion + + +class VersionScheme(object): + def __init__(self, key, matcher, suggester=None): + self.key = key + self.matcher = matcher + self.suggester = suggester + + def is_valid_version(self, s): + try: + self.matcher.version_class(s) + result = True + except UnsupportedVersionError: + result = False + return result + + def is_valid_matcher(self, s): + try: + self.matcher(s) + result = True + except UnsupportedVersionError: + result = False + return result + + def is_valid_constraint_list(self, s): + """ + Used for processing some metadata fields + """ + return self.is_valid_matcher('dummy_name (%s)' % s) + + def suggest(self, s): + if self.suggester is None: + result = None + else: + result = self.suggester(s) + return result + +_SCHEMES = { + 'normalized': VersionScheme(_normalized_key, NormalizedMatcher, + _suggest_normalized_version), + 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s), + 'semantic': VersionScheme(_semantic_key, SemanticMatcher, + _suggest_semantic_version), +} + +_SCHEMES['default'] = _SCHEMES['normalized'] + + +def get_scheme(name): + if name not in _SCHEMES: + raise ValueError('unknown scheme name: %r' % name) + return _SCHEMES[name] diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/w32.exe b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/w32.exe new file mode 100644 index 0000000000000000000000000000000000000000..732215a9d34ccb7b417d637a7646d9b843ecafa8 GIT binary patch literal 89088 zcmeFae|S?>wm*FGqitH!CO`{C3REl(SahnPNDYM`O{q#T7)Xdvz$<hz5gjb&0FD$| zPs=nt#$jgk-tk`NUb*PJIy$3wMg@OiORy=5PEkZ0P^;6cyPm2=Y8#~Xyq~pC(iU~@ z_dd`2ywCg3TS)e=wSTX@_S$Q$y;FJf9>F9Cf*Bu86NCeB>CeT#|L3nblC!40kR?2m z{>H@z3`^g*ct!B1Tk<#8{Ol(+x7?n8>n(TO@iQ_1pEl;#NO$D_^p5<p8@>6r|7^p? zD>5@P3KB)%InlPUc<U{5lb_$uJ2!U@!dIxT&CTYnHuoCtzCL#`-1%8w%`JjkymkHD zpYr&>Cg9H}+(GW%^wV2|ROUbGfyXTfOART)i?<!WISJD#7!6|#8G`TvV*Xu^z32+K zc6>T0?9%;4K}Zn{6fx`yPa}*$9C+R!7zI~72c&$InY+UdMGBkF3c`HyxD3K09`bzW z?_q;rO&5ec#{?noJ4vI19bbHBt~vx^h2FH$V8i|^#EsiUg#JboP3@w-(&Uf&%NK<8 zSJZ5{MZ852W)~s>WeT(LIf&1wKNqULLI)GN_(-E-D)X~ZK=1;t<%*guHMhdg`-(mb zHzDv1KBN9zR9?--O+N$ReOXAr81V9z!X5SJ5`=3<1^<8V|AP@&sr2}Qvp;hQT24iW zOHg|EiZd1ojV;oo#(r^ba2`^8T22{~_UQ@YMZp7O1R*2@?SerFE~TuJB_wDaYC0h8 zfONF1t%{=H`W`bdYp+>YBsg9Ty9ec3iy+O3xa}TIvPK#Q&udyx1MvwG0(#iaGC|N| zJ#3?<Y4YLRkU`54s9BYRjyISA>){9$tW;Y34lPnX=&>D4X~|k7c$TwEfOzs@Yh#Nz z`FV;`(w!E`sKg@`2E}bDY>ku^4XS@tV(WO*<eu67;0Clk;vRHp!Qf<+5w0B!*Y>g? zYH=KK#%%Yu1~&m>IlB^#2^syGG|2AB1(}4aOcaC%!)}%`I7AIC2(Ro3yW`GSttng^ z_xb=ECor!L{-PNO>_ulJ3%fm=O0X!s%)$GZ?~I94l-^KEAX0n$?4wGpr7&i4#~)OB zQD%2NrWUKtZuYT1Vnu}AeF`cSgx>Rkk@}Lg{WltgT76VeA2aic`cTnpXrt2WXmJkM z9%u<Rp-*9{$HQ)>Xm?McyDyZ28Ux7mpxy?mnmvzMMr-85vkRrJLaDRx>|I7je+cM+ zj{RJ(3Vrgke;W@#D!y%U%fQLtlPKTAzWtVuOQdXpwsy6eRjt^cZ%0D4bF7$F;f!th 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ð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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/w64.exe b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/w64.exe new file mode 100644 index 0000000000000000000000000000000000000000..c41bd0a011fd760ce20ba795d9e535e0d2c39876 GIT binary patch literal 99328 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 B=L-M; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/wheel.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/wheel.py new file mode 100644 index 0000000..b04bfae --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distlib/wheel.py @@ -0,0 +1,988 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2017 Vinay Sajip. +# Licensed to the Python Software Foundation under a contributor agreement. +# See LICENSE.txt and CONTRIBUTORS.txt. +# +from __future__ import unicode_literals + +import base64 +import codecs +import datetime +import distutils.util +from email import message_from_file +import hashlib +import imp +import json +import logging +import os +import posixpath +import re +import shutil +import sys +import tempfile +import zipfile + +from . import __version__, DistlibException +from .compat import sysconfig, ZipFile, fsdecode, text_type, filter +from .database import InstalledDistribution +from .metadata import Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME +from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, + cached_property, get_cache_base, read_exports, tempdir) +from .version import NormalizedVersion, UnsupportedVersionError + +logger = logging.getLogger(__name__) + +cache = None # created when needed + +if hasattr(sys, 'pypy_version_info'): # pragma: no cover + IMP_PREFIX = 'pp' +elif sys.platform.startswith('java'): # pragma: no cover + IMP_PREFIX = 'jy' +elif sys.platform == 'cli': # pragma: no cover + IMP_PREFIX = 'ip' +else: + IMP_PREFIX = 'cp' + +VER_SUFFIX = sysconfig.get_config_var('py_version_nodot') +if not VER_SUFFIX: # pragma: no cover + VER_SUFFIX = '%s%s' % sys.version_info[:2] +PYVER = 'py' + VER_SUFFIX +IMPVER = IMP_PREFIX + VER_SUFFIX + +ARCH = distutils.util.get_platform().replace('-', '_').replace('.', '_') + +ABI = sysconfig.get_config_var('SOABI') +if ABI and ABI.startswith('cpython-'): + ABI = ABI.replace('cpython-', 'cp') +else: + def _derive_abi(): + parts = ['cp', VER_SUFFIX] + if sysconfig.get_config_var('Py_DEBUG'): + parts.append('d') + if sysconfig.get_config_var('WITH_PYMALLOC'): + parts.append('m') + if sysconfig.get_config_var('Py_UNICODE_SIZE') == 4: + parts.append('u') + return ''.join(parts) + ABI = _derive_abi() + del _derive_abi + +FILENAME_RE = re.compile(r''' +(?P<nm>[^-]+) +-(?P<vn>\d+[^-]*) +(-(?P<bn>\d+[^-]*))? +-(?P<py>\w+\d+(\.\w+\d+)*) +-(?P<bi>\w+) +-(?P<ar>\w+(\.\w+)*) +\.whl$ +''', re.IGNORECASE | re.VERBOSE) + +NAME_VERSION_RE = re.compile(r''' +(?P<nm>[^-]+) +-(?P<vn>\d+[^-]*) +(-(?P<bn>\d+[^-]*))?$ +''', re.IGNORECASE | re.VERBOSE) + +SHEBANG_RE = re.compile(br'\s*#![^\r\n]*') +SHEBANG_DETAIL_RE = re.compile(br'^(\s*#!("[^"]+"|\S+))\s+(.*)$') +SHEBANG_PYTHON = b'#!python' +SHEBANG_PYTHONW = b'#!pythonw' + +if os.sep == '/': + to_posix = lambda o: o +else: + to_posix = lambda o: o.replace(os.sep, '/') + + +class Mounter(object): + def __init__(self): + self.impure_wheels = {} + self.libs = {} + + def add(self, pathname, extensions): + self.impure_wheels[pathname] = extensions + self.libs.update(extensions) + + def remove(self, pathname): + extensions = self.impure_wheels.pop(pathname) + for k, v in extensions: + if k in self.libs: + del self.libs[k] + + def find_module(self, fullname, path=None): + if fullname in self.libs: + result = self + else: + result = None + return result + + def load_module(self, fullname): + if fullname in sys.modules: + result = sys.modules[fullname] + 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.__loader__ = self + parts = fullname.rsplit('.', 1) + if len(parts) > 1: + result.__package__ = parts[0] + return result + +_hook = Mounter() + + +class Wheel(object): + """ + Class to build and install from Wheel files (PEP 427). + """ + + wheel_version = (1, 1) + hash_kind = 'sha256' + + def __init__(self, filename=None, sign=False, verify=False): + """ + Initialise an instance using a (valid) filename. + """ + self.sign = sign + self.should_verify = verify + self.buildver = '' + self.pyver = [PYVER] + self.abi = ['none'] + self.arch = ['any'] + self.dirname = os.getcwd() + if filename is None: + self.name = 'dummy' + self.version = '0.1' + self._filename = self.filename + else: + m = NAME_VERSION_RE.match(filename) + if m: + info = m.groupdict('') + self.name = info['nm'] + # Reinstate the local version separator + self.version = info['vn'].replace('_', '-') + self.buildver = info['bn'] + self._filename = self.filename + else: + dirname, filename = os.path.split(filename) + m = FILENAME_RE.match(filename) + if not m: + raise DistlibException('Invalid name or ' + 'filename: %r' % filename) + if dirname: + self.dirname = os.path.abspath(dirname) + self._filename = filename + info = m.groupdict('') + self.name = info['nm'] + self.version = info['vn'] + self.buildver = info['bn'] + self.pyver = info['py'].split('.') + self.abi = info['bi'].split('.') + self.arch = info['ar'].split('.') + + @property + def filename(self): + """ + Build and return a filename from the various components. + """ + if self.buildver: + buildver = '-' + self.buildver + else: + buildver = '' + pyver = '.'.join(self.pyver) + abi = '.'.join(self.abi) + arch = '.'.join(self.arch) + # replace - with _ as a local version separator + version = self.version.replace('-', '_') + return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, + pyver, abi, arch) + + @property + def exists(self): + path = os.path.join(self.dirname, self.filename) + return os.path.isfile(path) + + @property + def tags(self): + for pyver in self.pyver: + for abi in self.abi: + for arch in self.arch: + yield pyver, abi, arch + + @cached_property + def metadata(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + wrapper = codecs.getreader('utf-8') + with ZipFile(pathname, 'r') as zf: + wheel_metadata = self.get_wheel_metadata(zf) + wv = wheel_metadata['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + if file_version < (1, 1): + fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, 'METADATA'] + else: + fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] + result = None + for fn in fns: + try: + metadata_filename = posixpath.join(info_dir, fn) + with zf.open(metadata_filename) as bf: + wf = wrapper(bf) + result = Metadata(fileobj=wf) + if result: + break + except KeyError: + pass + if not result: + raise ValueError('Invalid wheel, because metadata is ' + 'missing: looked in %s' % ', '.join(fns)) + return result + + def get_wheel_metadata(self, zf): + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + metadata_filename = posixpath.join(info_dir, 'WHEEL') + with zf.open(metadata_filename) as bf: + wf = codecs.getreader('utf-8')(bf) + message = message_from_file(wf) + return dict(message) + + @cached_property + def info(self): + pathname = os.path.join(self.dirname, self.filename) + with ZipFile(pathname, 'r') as zf: + result = self.get_wheel_metadata(zf) + return result + + def process_shebang(self, data): + m = SHEBANG_RE.match(data) + if m: + end = m.end() + shebang, data_after_shebang = data[:end], data[end:] + # Preserve any arguments after the interpreter + if b'pythonw' in shebang.lower(): + shebang_python = SHEBANG_PYTHONW + else: + shebang_python = SHEBANG_PYTHON + m = SHEBANG_DETAIL_RE.match(shebang) + if m: + args = b' ' + m.groups()[-1] + else: + args = b'' + shebang = shebang_python + args + data = shebang + data_after_shebang + else: + cr = data.find(b'\r') + lf = data.find(b'\n') + if cr < 0 or cr > lf: + term = b'\n' + else: + if data[cr:cr + 2] == b'\r\n': + term = b'\r\n' + else: + term = b'\r' + data = SHEBANG_PYTHON + term + data + return data + + def get_hash(self, data, hash_kind=None): + if hash_kind is None: + hash_kind = self.hash_kind + try: + hasher = getattr(hashlib, hash_kind) + except AttributeError: + raise DistlibException('Unsupported hash algorithm: %r' % hash_kind) + result = hasher(data).digest() + result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii') + return hash_kind, result + + def write_record(self, records, record_path, base): + records = list(records) # make a copy for sorting + p = to_posix(os.path.relpath(record_path, base)) + records.append((p, '', '')) + records.sort() + with CSVWriter(record_path) as writer: + for row in records: + writer.writerow(row) + + def write_records(self, info, libdir, archive_paths): + records = [] + distinfo, info_dir = info + hasher = getattr(hashlib, self.hash_kind) + for ap, p in archive_paths: + with open(p, 'rb') as f: + data = f.read() + digest = '%s=%s' % self.get_hash(data) + size = os.path.getsize(p) + records.append((ap, digest, size)) + + p = os.path.join(distinfo, 'RECORD') + self.write_record(records, p, libdir) + ap = to_posix(os.path.join(info_dir, 'RECORD')) + archive_paths.append((ap, p)) + + def build_zip(self, pathname, archive_paths): + with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf: + for ap, p in archive_paths: + logger.debug('Wrote %s to %s in wheel', p, ap) + zf.write(p, ap) + + def build(self, paths, tags=None, wheel_version=None): + """ + Build a wheel from files in specified paths, and use any specified tags + when determining the name of the wheel. + """ + if tags is None: + tags = {} + + libkey = list(filter(lambda o: o in paths, ('purelib', 'platlib')))[0] + if libkey == 'platlib': + is_pure = 'false' + default_pyver = [IMPVER] + default_abi = [ABI] + default_arch = [ARCH] + else: + is_pure = 'true' + default_pyver = [PYVER] + default_abi = ['none'] + default_arch = ['any'] + + self.pyver = tags.get('pyver', default_pyver) + self.abi = tags.get('abi', default_abi) + self.arch = tags.get('arch', default_arch) + + libdir = paths[libkey] + + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + archive_paths = [] + + # First, stuff which is not in site-packages + for key in ('data', 'headers', 'scripts'): + if key not in paths: + continue + path = paths[key] + if os.path.isdir(path): + for root, dirs, files in os.walk(path): + for fn in files: + p = fsdecode(os.path.join(root, fn)) + rp = os.path.relpath(p, path) + ap = to_posix(os.path.join(data_dir, key, rp)) + archive_paths.append((ap, p)) + if key == 'scripts' and not p.endswith('.exe'): + with open(p, 'rb') as f: + data = f.read() + data = self.process_shebang(data) + with open(p, 'wb') as f: + f.write(data) + + # Now, stuff which is in site-packages, other than the + # distinfo stuff. + path = libdir + distinfo = None + for root, dirs, files in os.walk(path): + if root == path: + # At the top level only, save distinfo for later + # and skip it for now + for i, dn in enumerate(dirs): + dn = fsdecode(dn) + if dn.endswith('.dist-info'): + distinfo = os.path.join(root, dn) + del dirs[i] + break + assert distinfo, '.dist-info directory expected, not found' + + for fn in files: + # comment out next suite to leave .pyc files in + if fsdecode(fn).endswith(('.pyc', '.pyo')): + continue + p = os.path.join(root, fn) + rp = to_posix(os.path.relpath(p, path)) + archive_paths.append((rp, p)) + + # Now distinfo. Assumed to be flat, i.e. os.listdir is enough. + files = os.listdir(distinfo) + for fn in files: + if fn not in ('RECORD', 'INSTALLER', 'SHARED', 'WHEEL'): + p = fsdecode(os.path.join(distinfo, fn)) + ap = to_posix(os.path.join(info_dir, fn)) + archive_paths.append((ap, p)) + + wheel_metadata = [ + 'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version), + 'Generator: distlib %s' % __version__, + 'Root-Is-Purelib: %s' % is_pure, + ] + for pyver, abi, arch in self.tags: + wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch)) + p = os.path.join(distinfo, 'WHEEL') + with open(p, 'w') as f: + f.write('\n'.join(wheel_metadata)) + ap = to_posix(os.path.join(info_dir, 'WHEEL')) + archive_paths.append((ap, p)) + + # Now, at last, RECORD. + # Paths in here are archive paths - nothing else makes sense. + self.write_records((distinfo, info_dir), libdir, archive_paths) + # Now, ready to build the zip file + pathname = os.path.join(self.dirname, self.filename) + self.build_zip(pathname, archive_paths) + return pathname + + def install(self, paths, maker, **kwargs): + """ + Install a wheel to the specified paths. If kwarg ``warner`` is + specified, it should be a callable, which will be called with two + tuples indicating the wheel version of this software and the wheel + version in the file, if there is a discrepancy in the versions. + This can be used to issue any warnings to raise any exceptions. + If kwarg ``lib_only`` is True, only the purelib/platlib files are + installed, and the headers, scripts, data and dist-info metadata are + not written. If kwarg ``bytecode_hashed_invalidation`` is True, written + bytecode will try to use file-hash based invalidation (PEP-552) on + supported interpreter versions (CPython 2.7+). + + The return value is a :class:`InstalledDistribution` instance unless + ``options.lib_only`` is True, in which case the return value is ``None``. + """ + + dry_run = maker.dry_run + warner = kwargs.get('warner') + lib_only = kwargs.get('lib_only', False) + bc_hashed_invalidation = kwargs.get('bytecode_hashed_invalidation', False) + + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') + record_name = posixpath.join(info_dir, 'RECORD') + + wrapper = codecs.getreader('utf-8') + + with ZipFile(pathname, 'r') as zf: + with zf.open(wheel_metadata_name) as bwf: + wf = wrapper(bwf) + message = message_from_file(wf) + wv = message['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + if (file_version != self.wheel_version) and warner: + warner(self.wheel_version, file_version) + + if message['Root-Is-Purelib'] == 'true': + libdir = paths['purelib'] + else: + libdir = paths['platlib'] + + records = {} + with zf.open(record_name) as bf: + with CSVReader(stream=bf) as reader: + for row in reader: + p = row[0] + records[p] = row + + data_pfx = posixpath.join(data_dir, '') + info_pfx = posixpath.join(info_dir, '') + script_pfx = posixpath.join(data_dir, 'scripts', '') + + # make a new instance rather than a copy of maker's, + # as we mutate it + fileop = FileOperator(dry_run=dry_run) + fileop.record = True # so we can rollback if needed + + bc = not sys.dont_write_bytecode # Double negatives. Lovely! + + outfiles = [] # for RECORD writing + + # for script copying/shebang processing + workdir = tempfile.mkdtemp() + # set target dir later + # we default add_launchers to False, as the + # Python Launcher should be used instead + maker.source_dir = workdir + maker.target_dir = None + try: + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + # The signature file won't be in RECORD, + # and we don't currently don't do anything with it + if u_arcname.endswith('/RECORD.jws'): + continue + row = records[u_arcname] + if row[2] and str(zinfo.file_size) != row[2]: + raise DistlibException('size mismatch for ' + '%s' % u_arcname) + if row[1]: + kind, value = row[1].split('=', 1) + with zf.open(arcname) as bf: + data = bf.read() + _, digest = self.get_hash(data, kind) + if digest != value: + raise DistlibException('digest mismatch for ' + '%s' % arcname) + + if lib_only and u_arcname.startswith((info_pfx, data_pfx)): + logger.debug('lib_only: skipping %s', u_arcname) + continue + is_script = (u_arcname.startswith(script_pfx) + and not u_arcname.endswith('.exe')) + + if u_arcname.startswith(data_pfx): + _, where, rp = u_arcname.split('/', 2) + outfile = os.path.join(paths[where], convert_path(rp)) + else: + # meant for site-packages. + if u_arcname in (wheel_metadata_name, record_name): + continue + outfile = os.path.join(libdir, convert_path(u_arcname)) + if not is_script: + with zf.open(arcname) as bf: + fileop.copy_stream(bf, outfile) + outfiles.append(outfile) + # Double check the digest of the written file + if not dry_run and row[1]: + with open(outfile, 'rb') as bf: + data = bf.read() + _, newdigest = self.get_hash(data, kind) + if newdigest != digest: + raise DistlibException('digest mismatch ' + 'on write for ' + '%s' % outfile) + if bc and outfile.endswith('.py'): + try: + pyc = fileop.byte_compile(outfile, + hashed_invalidation=bc_hashed_invalidation) + outfiles.append(pyc) + except Exception: + # Don't give up if byte-compilation fails, + # but log it and perhaps warn the user + logger.warning('Byte-compilation failed', + exc_info=True) + else: + fn = os.path.basename(convert_path(arcname)) + workname = os.path.join(workdir, fn) + with zf.open(arcname) as bf: + fileop.copy_stream(bf, workname) + + dn, fn = os.path.split(outfile) + maker.target_dir = dn + filenames = maker.make(fn) + fileop.set_executable_mode(filenames) + outfiles.extend(filenames) + + if lib_only: + logger.debug('lib_only: returning None') + dist = None + else: + # Generate scripts + + # Try to get pydist.json so we can see if there are + # any commands to generate. If this fails (e.g. because + # of a legacy wheel), log a warning but don't give up. + commands = None + file_version = self.info['Wheel-Version'] + if file_version == '1.0': + # Use legacy info + ep = posixpath.join(info_dir, 'entry_points.txt') + try: + with zf.open(ep) as bwf: + epdata = read_exports(bwf) + commands = {} + for key in ('console', 'gui'): + k = '%s_scripts' % key + if k in epdata: + commands['wrap_%s' % key] = d = {} + for v in epdata[k].values(): + s = '%s:%s' % (v.prefix, v.suffix) + if v.flags: + s += ' %s' % v.flags + d[v.name] = s + except Exception: + logger.warning('Unable to read legacy script ' + 'metadata, so cannot generate ' + 'scripts') + else: + try: + with zf.open(metadata_name) as bwf: + wf = wrapper(bwf) + commands = json.load(wf).get('extensions') + if commands: + commands = commands.get('python.commands') + except Exception: + logger.warning('Unable to read JSON metadata, so ' + 'cannot generate scripts') + if commands: + console_scripts = commands.get('wrap_console', {}) + gui_scripts = commands.get('wrap_gui', {}) + if console_scripts or gui_scripts: + script_dir = paths.get('scripts', '') + if not os.path.isdir(script_dir): + raise ValueError('Valid script path not ' + 'specified') + maker.target_dir = script_dir + for k, v in console_scripts.items(): + script = '%s = %s' % (k, v) + filenames = maker.make(script) + fileop.set_executable_mode(filenames) + + if gui_scripts: + options = {'gui': True } + for k, v in gui_scripts.items(): + script = '%s = %s' % (k, v) + filenames = maker.make(script, options) + fileop.set_executable_mode(filenames) + + p = os.path.join(libdir, info_dir) + dist = InstalledDistribution(p) + + # Write SHARED + paths = dict(paths) # don't change passed in dict + del paths['purelib'] + del paths['platlib'] + paths['lib'] = libdir + p = dist.write_shared_locations(paths, dry_run) + if p: + outfiles.append(p) + + # Write RECORD + dist.write_installed_files(outfiles, paths['prefix'], + dry_run) + return dist + except Exception: # pragma: no cover + logger.exception('installation failed.') + fileop.rollback() + raise + finally: + shutil.rmtree(workdir) + + def _get_dylib_cache(self): + global cache + if cache is None: + # Use native string to avoid issues on 2.x: see Python #20140. + base = os.path.join(get_cache_base(), str('dylib-cache'), + sys.version[:3]) + cache = Cache(base) + return cache + + def _get_extensions(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + arcname = posixpath.join(info_dir, 'EXTENSIONS') + wrapper = codecs.getreader('utf-8') + result = [] + with ZipFile(pathname, 'r') as zf: + try: + with zf.open(arcname) as bf: + wf = wrapper(bf) + extensions = json.load(wf) + cache = self._get_dylib_cache() + prefix = cache.prefix_to_dir(pathname) + cache_base = os.path.join(cache.base, prefix) + if not os.path.isdir(cache_base): + os.makedirs(cache_base) + for name, relpath in extensions.items(): + dest = os.path.join(cache_base, convert_path(relpath)) + if not os.path.exists(dest): + extract = True + else: + file_time = os.stat(dest).st_mtime + file_time = datetime.datetime.fromtimestamp(file_time) + info = zf.getinfo(relpath) + wheel_time = datetime.datetime(*info.date_time) + extract = wheel_time > file_time + if extract: + zf.extract(relpath, cache_base) + result.append((name, dest)) + except KeyError: + pass + return result + + def is_compatible(self): + """ + Determine if a wheel is compatible with the running system. + """ + return is_compatible(self) + + def is_mountable(self): + """ + Determine if a wheel is asserted as mountable by its metadata. + """ + return True # for now - metadata details TBD + + def mount(self, append=False): + pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) + if not self.is_compatible(): + msg = 'Wheel %s not compatible with this Python.' % pathname + raise DistlibException(msg) + if not self.is_mountable(): + msg = 'Wheel %s is marked as not mountable.' % pathname + raise DistlibException(msg) + if pathname in sys.path: + logger.debug('%s already in path', pathname) + else: + if append: + sys.path.append(pathname) + else: + sys.path.insert(0, pathname) + extensions = self._get_extensions() + if extensions: + if _hook not in sys.meta_path: + sys.meta_path.append(_hook) + _hook.add(pathname, extensions) + + def unmount(self): + pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) + if pathname not in sys.path: + logger.debug('%s not in path', pathname) + else: + sys.path.remove(pathname) + if pathname in _hook.impure_wheels: + _hook.remove(pathname) + if not _hook.impure_wheels: + if _hook in sys.meta_path: + sys.meta_path.remove(_hook) + + def verify(self): + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + data_dir = '%s.data' % name_ver + info_dir = '%s.dist-info' % name_ver + + metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') + record_name = posixpath.join(info_dir, 'RECORD') + + wrapper = codecs.getreader('utf-8') + + with ZipFile(pathname, 'r') as zf: + with zf.open(wheel_metadata_name) as bwf: + wf = wrapper(bwf) + message = message_from_file(wf) + wv = message['Wheel-Version'].split('.', 1) + file_version = tuple([int(i) for i in wv]) + # TODO version verification + + records = {} + with zf.open(record_name) as bf: + with CSVReader(stream=bf) as reader: + for row in reader: + p = row[0] + records[p] = row + + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + if '..' in u_arcname: + raise DistlibException('invalid entry in ' + 'wheel: %r' % u_arcname) + + # The signature file won't be in RECORD, + # and we don't currently don't do anything with it + if u_arcname.endswith('/RECORD.jws'): + continue + row = records[u_arcname] + if row[2] and str(zinfo.file_size) != row[2]: + raise DistlibException('size mismatch for ' + '%s' % u_arcname) + if row[1]: + kind, value = row[1].split('=', 1) + with zf.open(arcname) as bf: + data = bf.read() + _, digest = self.get_hash(data, kind) + if digest != value: + raise DistlibException('digest mismatch for ' + '%s' % arcname) + + def update(self, modifier, dest_dir=None, **kwargs): + """ + Update the contents of a wheel in a generic way. The modifier should + be a callable which expects a dictionary argument: its keys are + archive-entry paths, and its values are absolute filesystem paths + where the contents the corresponding archive entries can be found. The + modifier is free to change the contents of the files pointed to, add + new entries and remove entries, before returning. This method will + extract the entire contents of the wheel to a temporary location, call + the modifier, and then use the passed (and possibly updated) + dictionary to write a new wheel. If ``dest_dir`` is specified, the new + wheel is written there -- otherwise, the original wheel is overwritten. + + The modifier should return True if it updated the wheel, else False. + This method returns the same value the modifier returns. + """ + + def get_version(path_map, info_dir): + version = path = None + key = '%s/%s' % (info_dir, METADATA_FILENAME) + if key not in path_map: + key = '%s/PKG-INFO' % info_dir + if key in path_map: + path = path_map[key] + version = Metadata(path=path).version + return version, path + + def update_version(version, path): + updated = None + try: + v = NormalizedVersion(version) + i = version.find('-') + if i < 0: + updated = '%s+1' % version + else: + parts = [int(s) for s in version[i + 1:].split('.')] + parts[-1] += 1 + updated = '%s+%s' % (version[:i], + '.'.join(str(i) for i in parts)) + except UnsupportedVersionError: + logger.debug('Cannot update non-compliant (PEP-440) ' + 'version %r', version) + if updated: + md = Metadata(path=path) + md.version = updated + legacy = not path.endswith(METADATA_FILENAME) + md.write(path=path, legacy=legacy) + logger.debug('Version updated from %r to %r', version, + updated) + + pathname = os.path.join(self.dirname, self.filename) + name_ver = '%s-%s' % (self.name, self.version) + info_dir = '%s.dist-info' % name_ver + record_name = posixpath.join(info_dir, 'RECORD') + with tempdir() as workdir: + with ZipFile(pathname, 'r') as zf: + path_map = {} + for zinfo in zf.infolist(): + arcname = zinfo.filename + if isinstance(arcname, text_type): + u_arcname = arcname + else: + u_arcname = arcname.decode('utf-8') + if u_arcname == record_name: + continue + if '..' in u_arcname: + raise DistlibException('invalid entry in ' + 'wheel: %r' % u_arcname) + zf.extract(zinfo, workdir) + path = os.path.join(workdir, convert_path(u_arcname)) + path_map[u_arcname] = path + + # Remember the version. + original_version, _ = get_version(path_map, info_dir) + # Files extracted. Call the modifier. + modified = modifier(path_map, **kwargs) + if modified: + # Something changed - need to build a new wheel. + current_version, path = get_version(path_map, info_dir) + if current_version and (current_version == original_version): + # Add or update local version to signify changes. + update_version(current_version, path) + # Decide where the new wheel goes. + if dest_dir is None: + fd, newpath = tempfile.mkstemp(suffix='.whl', + prefix='wheel-update-', + dir=workdir) + os.close(fd) + else: + if not os.path.isdir(dest_dir): + raise DistlibException('Not a directory: %r' % dest_dir) + newpath = os.path.join(dest_dir, self.filename) + archive_paths = list(path_map.items()) + distinfo = os.path.join(workdir, info_dir) + info = distinfo, info_dir + self.write_records(info, workdir, archive_paths) + self.build_zip(newpath, archive_paths) + if dest_dir is None: + shutil.copyfile(newpath, pathname) + return modified + +def compatible_tags(): + """ + Return (pyver, abi, arch) tuples compatible with this Python. + """ + versions = [VER_SUFFIX] + major = VER_SUFFIX[0] + for minor in range(sys.version_info[1] - 1, - 1, -1): + versions.append(''.join([major, str(minor)])) + + abis = [] + for suffix, _, _ in imp.get_suffixes(): + if suffix.startswith('.abi'): + abis.append(suffix.split('.', 2)[1]) + abis.sort() + if ABI != 'none': + abis.insert(0, ABI) + abis.append('none') + result = [] + + arches = [ARCH] + if sys.platform == 'darwin': + m = re.match(r'(\w+)_(\d+)_(\d+)_(\w+)$', ARCH) + if m: + name, major, minor, arch = m.groups() + minor = int(minor) + matches = [arch] + if arch in ('i386', 'ppc'): + matches.append('fat') + if arch in ('i386', 'ppc', 'x86_64'): + matches.append('fat3') + if arch in ('ppc64', 'x86_64'): + matches.append('fat64') + if arch in ('i386', 'x86_64'): + matches.append('intel') + if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'): + matches.append('universal') + while minor >= 0: + for match in matches: + s = '%s_%s_%s_%s' % (name, major, minor, match) + if s != ARCH: # already there + arches.append(s) + minor -= 1 + + # Most specific - our Python version, ABI and arch + for abi in abis: + for arch in arches: + result.append((''.join((IMP_PREFIX, versions[0])), abi, arch)) + + # where no ABI / arch dependency, but IMP_PREFIX dependency + for i, version in enumerate(versions): + result.append((''.join((IMP_PREFIX, version)), 'none', 'any')) + if i == 0: + result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any')) + + # no IMP_PREFIX, ABI or arch dependency + for i, version in enumerate(versions): + result.append((''.join(('py', version)), 'none', 'any')) + if i == 0: + result.append((''.join(('py', version[0])), 'none', 'any')) + return set(result) + + +COMPATIBLE_TAGS = compatible_tags() + +del compatible_tags + + +def is_compatible(wheel, tags=None): + if not isinstance(wheel, Wheel): + wheel = Wheel(wheel) # assume it's a filename + result = False + if tags is None: + tags = COMPATIBLE_TAGS + for ver, abi, arch in tags: + if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch: + result = True + break + return result diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distro.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distro.py new file mode 100644 index 0000000..aa4defc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/distro.py @@ -0,0 +1,1197 @@ +# Copyright 2015,2016,2017 Nir Cohen +# +# 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. + +""" +The ``distro`` package (``distro`` stands for Linux Distribution) provides +information about the Linux distribution it runs on, such as a reliable +machine-readable distro ID, or version information. + +It is a renewed alternative implementation for Python's original +:py:func:`platform.linux_distribution` function, but it provides much more +functionality. An alternative implementation became necessary because Python +3.5 deprecated this function, and Python 3.7 is expected to remove it +altogether. Its predecessor function :py:func:`platform.dist` was already +deprecated since Python 2.6 and is also expected to be removed in Python 3.7. +Still, there are many cases in which access to OS distribution information +is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for +more information. +""" + +import os +import re +import sys +import json +import shlex +import logging +import argparse +import subprocess + + +_UNIXCONFDIR = os.environ.get('UNIXCONFDIR', '/etc') +_OS_RELEASE_BASENAME = 'os-release' + +#: Translation table for normalizing the "ID" attribute defined in os-release +#: files, for use by the :func:`distro.id` method. +#: +#: * Key: Value as defined in the os-release file, translated to lower case, +#: with blanks translated to underscores. +#: +#: * Value: Normalized value. +NORMALIZED_OS_ID = {} + +#: Translation table for normalizing the "Distributor ID" attribute returned by +#: the lsb_release command, for use by the :func:`distro.id` method. +#: +#: * Key: Value as returned by the lsb_release command, translated to lower +#: case, with blanks translated to underscores. +#: +#: * Value: Normalized value. +NORMALIZED_LSB_ID = { + 'enterpriseenterprise': 'oracle', # Oracle Enterprise Linux + 'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation + 'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server +} + +#: Translation table for normalizing the distro ID derived from the file name +#: of distro release files, for use by the :func:`distro.id` method. +#: +#: * Key: Value as derived from the file name of a distro release file, +#: translated to lower case, with blanks translated to underscores. +#: +#: * Value: Normalized value. +NORMALIZED_DISTRO_ID = { + 'redhat': 'rhel', # RHEL 6.x, 7.x +} + +# Pattern for content of distro release file (reversed) +_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile( + r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)') + +# Pattern for base file name of distro release file +_DISTRO_RELEASE_BASENAME_PATTERN = re.compile( + r'(\w+)[-_](release|version)$') + +# Base file names to be ignored when searching for distro release file +_DISTRO_RELEASE_IGNORE_BASENAMES = ( + 'debian_version', + 'lsb-release', + 'oem-release', + _OS_RELEASE_BASENAME, + 'system-release' +) + + +def linux_distribution(full_distribution_name=True): + """ + Return information about the current OS distribution as a tuple + ``(id_name, version, codename)`` with items as follows: + + * ``id_name``: If *full_distribution_name* is false, the result of + :func:`distro.id`. Otherwise, the result of :func:`distro.name`. + + * ``version``: The result of :func:`distro.version`. + + * ``codename``: 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 + its parameters. + + The data it returns may not exactly be the same, because it uses more data + sources than the original function, and that may lead to different data if + the OS distribution is not consistent across multiple data sources it + provides (there are indeed such distributions ...). + + Another reason for differences is the fact that the :func:`distro.id` + method normalizes the distro ID string to a reliable machine-readable value + for a number of popular OS distributions. + """ + return _distro.linux_distribution(full_distribution_name) + + +def id(): + """ + Return the distro ID of the current distribution, as a + machine-readable string. + + For a number of OS distributions, the returned distro ID value is + *reliable*, in the sense that it is documented and that it does not change + across releases of the distribution. + + This package maintains the following reliable distro ID values: + + ============== ========================================= + Distro ID Distribution + ============== ========================================= + "ubuntu" Ubuntu + "debian" Debian + "rhel" RedHat Enterprise Linux + "centos" CentOS + "fedora" Fedora + "sles" SUSE Linux Enterprise Server + "opensuse" openSUSE + "amazon" Amazon Linux + "arch" Arch Linux + "cloudlinux" CloudLinux OS + "exherbo" Exherbo Linux + "gentoo" GenToo Linux + "ibm_powerkvm" IBM PowerKVM + "kvmibm" KVM for IBM z Systems + "linuxmint" Linux Mint + "mageia" Mageia + "mandriva" Mandriva Linux + "parallels" Parallels + "pidora" Pidora + "raspbian" Raspbian + "oracle" Oracle Linux (and Oracle Enterprise Linux) + "scientific" Scientific Linux + "slackware" Slackware + "xenserver" XenServer + "openbsd" OpenBSD + "netbsd" NetBSD + "freebsd" FreeBSD + ============== ========================================= + + If you have a need to get distros for reliable IDs added into this set, + or if you find that the :func:`distro.id` function returns a different + distro ID for one of the listed distros, please create an issue in the + `distro issue tracker`_. + + **Lookup hierarchy and transformations:** + + First, the ID is obtained from the following sources, in the specified + order. The first available and non-empty value is used: + + * the value of the "ID" attribute of the os-release file, + + * the value of the "Distributor ID" attribute returned by the lsb_release + command, + + * the first part of the file name of the distro release file, + + The so determined ID value then passes the following transformations, + before it is returned by this method: + + * it is translated to lower case, + + * blanks (which should not be there anyway) are translated to underscores, + + * a normalization of the ID is performed, based upon + `normalization tables`_. The purpose of this normalization is to ensure + that the ID is as reliable as possible, even across incompatible changes + in the OS distributions. A common reason for an incompatible change is + the addition of an os-release file, or the addition of the lsb_release + command, with ID values that differ from what was previously determined + from the distro release file name. + """ + return _distro.id() + + +def name(pretty=False): + """ + Return the name of the current OS distribution, as a human-readable + string. + + If *pretty* is false, the name is returned without version or codename. + (e.g. "CentOS Linux") + + If *pretty* is true, the version and codename are appended. + (e.g. "CentOS Linux 7.1.1503 (Core)") + + **Lookup hierarchy:** + + The name is obtained from the following sources, in the specified order. + The first available and non-empty value is used: + + * If *pretty* is false: + + - the value of the "NAME" attribute of the os-release file, + + - the value of the "Distributor ID" attribute returned by the lsb_release + command, + + - the value of the "<name>" field of the distro release file. + + * If *pretty* is true: + + - the value of the "PRETTY_NAME" attribute of the os-release file, + + - the value of the "Description" attribute returned by the lsb_release + command, + + - the value of the "<name>" field of the distro release file, appended + with the value of the pretty version ("<version_id>" and "<codename>" + fields) of the distro release file, if available. + """ + return _distro.name(pretty) + + +def version(pretty=False, best=False): + """ + Return the version of the current OS distribution, as a human-readable + string. + + If *pretty* is false, the version is returned without codename (e.g. + "7.0"). + + If *pretty* is true, the codename in parenthesis is appended, if the + codename is non-empty (e.g. "7.0 (Maipo)"). + + Some distributions provide version numbers with different precisions in + the different sources of distribution information. Examining the different + sources in a fixed priority order does not always yield the most precise + version (e.g. for Debian 8.2, or CentOS 7.1). + + The *best* parameter can be used to control the approach for the returned + version: + + If *best* is false, the first non-empty version number in priority order of + the examined sources is returned. + + If *best* is true, the most precise version number out of all examined + sources is returned. + + **Lookup hierarchy:** + + In all cases, the version number is obtained from the following sources. + If *best* is false, this order represents the priority order: + + * the value of the "VERSION_ID" attribute of the os-release file, + * the value of the "Release" attribute returned by the lsb_release + command, + * the version number parsed from the "<version_id>" field of the first line + of the distro release file, + * the version number parsed from the "PRETTY_NAME" attribute of the + os-release file, if it follows the format of the distro release files. + * the version number parsed from the "Description" attribute returned by + the lsb_release command, if it follows the format of the distro release + files. + """ + return _distro.version(pretty, best) + + +def version_parts(best=False): + """ + Return the version of the current OS distribution as a tuple + ``(major, minor, build_number)`` with items as follows: + + * ``major``: The result of :func:`distro.major_version`. + + * ``minor``: The result of :func:`distro.minor_version`. + + * ``build_number``: The result of :func:`distro.build_number`. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.version_parts(best) + + +def major_version(best=False): + """ + Return the major version of the current OS distribution, as a string, + if provided. + Otherwise, the empty string is returned. The major version is the first + part of the dot-separated version string. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.major_version(best) + + +def minor_version(best=False): + """ + Return the minor version of the current OS distribution, as a string, + if provided. + Otherwise, the empty string is returned. The minor version is the second + part of the dot-separated version string. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.minor_version(best) + + +def build_number(best=False): + """ + Return the build number of the current OS distribution, as a string, + if provided. + Otherwise, the empty string is returned. The build number is the third part + of the dot-separated version string. + + For a description of the *best* parameter, see the :func:`distro.version` + method. + """ + return _distro.build_number(best) + + +def like(): + """ + Return a space-separated list of distro IDs of distributions that are + closely related to the current OS distribution in regards to packaging + and programming interfaces, for example distributions the current + distribution is a derivative from. + + **Lookup hierarchy:** + + This information item is only provided by the os-release file. + For details, see the description of the "ID_LIKE" attribute in the + `os-release man page + <http://www.freedesktop.org/software/systemd/man/os-release.html>`_. + """ + return _distro.like() + + +def codename(): + """ + Return the codename for the release of the current OS distribution, + as a string. + + If the distribution does not have a codename, an empty string is returned. + + Note that the returned codename is not always really a codename. For + example, openSUSE returns "x86_64". This function does not handle such + cases in any special way and just returns the string it finds, if any. + + **Lookup hierarchy:** + + * the codename within the "VERSION" attribute of the os-release file, if + provided, + + * the value of the "Codename" attribute returned by the lsb_release + command, + + * the value of the "<codename>" field of the distro release file. + """ + return _distro.codename() + + +def info(pretty=False, best=False): + """ + Return certain machine-readable information items about the current OS + distribution in a dictionary, as shown in the following example: + + .. sourcecode:: python + + { + 'id': 'rhel', + 'version': '7.0', + 'version_parts': { + 'major': '7', + 'minor': '0', + 'build_number': '' + }, + 'like': 'fedora', + 'codename': 'Maipo' + } + + The dictionary structure and keys are always the same, regardless of which + information items are available in the underlying data sources. The values + for the various keys are as follows: + + * ``id``: The result of :func:`distro.id`. + + * ``version``: The result of :func:`distro.version`. + + * ``version_parts -> major``: The result of :func:`distro.major_version`. + + * ``version_parts -> minor``: The result of :func:`distro.minor_version`. + + * ``version_parts -> build_number``: The result of + :func:`distro.build_number`. + + * ``like``: The result of :func:`distro.like`. + + * ``codename``: The result of :func:`distro.codename`. + + For a description of the *pretty* and *best* parameters, see the + :func:`distro.version` method. + """ + return _distro.info(pretty, best) + + +def os_release_info(): + """ + Return a dictionary containing key-value pairs for the information items + from the os-release file data source of the current OS distribution. + + See `os-release file`_ for details about these information items. + """ + return _distro.os_release_info() + + +def lsb_release_info(): + """ + Return a dictionary containing key-value pairs for the information items + from the lsb_release command data source of the current OS distribution. + + See `lsb_release command output`_ for details about these information + items. + """ + return _distro.lsb_release_info() + + +def distro_release_info(): + """ + Return a dictionary containing key-value pairs for the information items + from the distro release file data source of the current OS distribution. + + See `distro release file`_ for details about these information items. + """ + return _distro.distro_release_info() + + +def uname_info(): + """ + Return a dictionary containing key-value pairs for the information items + from the distro release file data source of the current OS distribution. + """ + return _distro.uname_info() + + +def os_release_attr(attribute): + """ + Return a single named information item from the os-release file data source + of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + + See `os-release file`_ for details about these information items. + """ + return _distro.os_release_attr(attribute) + + +def lsb_release_attr(attribute): + """ + Return a single named information item from the lsb_release command output + data source of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + + See `lsb_release command output`_ for details about these information + items. + """ + return _distro.lsb_release_attr(attribute) + + +def distro_release_attr(attribute): + """ + Return a single named information item from the distro release file + data source of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + + See `distro release file`_ for details about these information items. + """ + return _distro.distro_release_attr(attribute) + + +def uname_attr(attribute): + """ + Return a single named information item from the distro release file + data source of the current OS distribution. + + Parameters: + + * ``attribute`` (string): Key of the information item. + + Returns: + + * (string): Value of the information item, if the item exists. + The empty string, if the item does not exist. + """ + return _distro.uname_attr(attribute) + + +class cached_property(object): + """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): + self._fname = f.__name__ + self._f = f + + def __get__(self, obj, owner): + assert obj is not None, 'call {} on an instance'.format(self._fname) + ret = obj.__dict__[self._fname] = self._f(obj) + return ret + + +class LinuxDistribution(object): + """ + Provides information about a OS distribution. + + This package creates a private module-global instance of this class with + default initialization arguments, that is used by the + `consolidated accessor functions`_ and `single source accessor functions`_. + By using default initialization arguments, that module-global instance + returns data about the current OS distribution (i.e. the distro this + package runs on). + + Normally, it is not necessary to create additional instances of this class. + However, in situations where control is needed over the exact data sources + that are used, instances of this class can be created with a specific + distro release file, or a specific os-release file, or without invoking the + lsb_release command. + """ + + def __init__(self, + include_lsb=True, + os_release_file='', + distro_release_file='', + include_uname=True): + """ + The initialization method of this class gathers information from the + available data sources, and stores that in private instance attributes. + Subsequent access to the information items uses these private instance + attributes, so that the data sources are read only once. + + Parameters: + + * ``include_lsb`` (bool): Controls whether the + `lsb_release command output`_ is included as a data source. + + If the lsb_release command is not available in the program execution + path, the data source for the lsb_release command will be empty. + + * ``os_release_file`` (string): The path name of the + `os-release file`_ that is to be used as a data source. + + An empty string (the default) will cause the default path name to + be used (see `os-release file`_ for details). + + If the specified or defaulted os-release file does not exist, the + data source for the os-release file will be empty. + + * ``distro_release_file`` (string): The path name of the + `distro release file`_ that is to be used as a data source. + + An empty string (the default) will cause a default search algorithm + to be used (see `distro release file`_ for details). + + If the specified distro release file does not exist, or if no default + distro release file can be found, the data source for the distro + release file will be empty. + + * ``include_name`` (bool): Controls whether uname command output is + included as a data source. If the uname command is not available in + the program execution path the data source for the uname command will + be empty. + + Public instance attributes: + + * ``os_release_file`` (string): The path name of the + `os-release file`_ that is actually used as a data source. The + empty string if no distro release file is used as a data source. + + * ``distro_release_file`` (string): The path name of the + `distro release file`_ that is actually used as a data source. The + empty string if no distro release file is used as a data source. + + * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter. + This controls whether the lsb information will be loaded. + + * ``include_uname`` (bool): The result of the ``include_uname`` + parameter. This controls whether the uname information will + be loaded. + + Raises: + + * :py:exc:`IOError`: Some I/O issue with an os-release file or distro + release file. + + * :py:exc:`subprocess.CalledProcessError`: The lsb_release command had + some issue (other than not being available in the program execution + path). + + * :py:exc:`UnicodeError`: A data source has unexpected characters or + uses an unexpected encoding. + """ + self.os_release_file = os_release_file or \ + os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME) + self.distro_release_file = distro_release_file or '' # updated later + self.include_lsb = include_lsb + self.include_uname = include_uname + + def __repr__(self): + """Return repr of all info + """ + return \ + "LinuxDistribution(" \ + "os_release_file={self.os_release_file!r}, " \ + "distro_release_file={self.distro_release_file!r}, " \ + "include_lsb={self.include_lsb!r}, " \ + "include_uname={self.include_uname!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) + + def linux_distribution(self, full_distribution_name=True): + """ + Return information about the OS distribution that is compatible + with Python's :func:`platform.linux_distribution`, supporting a subset + of its parameters. + + For details, see :func:`distro.linux_distribution`. + """ + return ( + self.name() if full_distribution_name else self.id(), + self.version(), + self.codename() + ) + + def id(self): + """Return the distro ID of the OS distribution, as a string. + + For details, see :func:`distro.id`. + """ + def normalize(distro_id, table): + distro_id = distro_id.lower().replace(' ', '_') + return table.get(distro_id, distro_id) + + distro_id = self.os_release_attr('id') + if distro_id: + return normalize(distro_id, NORMALIZED_OS_ID) + + distro_id = self.lsb_release_attr('distributor_id') + if distro_id: + return normalize(distro_id, NORMALIZED_LSB_ID) + + distro_id = self.distro_release_attr('id') + if distro_id: + return normalize(distro_id, NORMALIZED_DISTRO_ID) + + distro_id = self.uname_attr('id') + if distro_id: + return normalize(distro_id, NORMALIZED_DISTRO_ID) + + return '' + + def name(self, pretty=False): + """ + Return the name of the OS distribution, as a string. + + For details, see :func:`distro.name`. + """ + name = self.os_release_attr('name') \ + or self.lsb_release_attr('distributor_id') \ + or self.distro_release_attr('name') \ + or self.uname_attr('name') + if pretty: + name = self.os_release_attr('pretty_name') \ + or self.lsb_release_attr('description') + if not name: + name = self.distro_release_attr('name') \ + or self.uname_attr('name') + version = self.version(pretty=True) + if version: + name = name + ' ' + version + return name or '' + + def version(self, pretty=False, best=False): + """ + Return the version of the OS distribution, as a string. + + For details, see :func:`distro.version`. + """ + versions = [ + self.os_release_attr('version_id'), + self.lsb_release_attr('release'), + self.distro_release_attr('version_id'), + self._parse_distro_release_content( + self.os_release_attr('pretty_name')).get('version_id', ''), + self._parse_distro_release_content( + self.lsb_release_attr('description')).get('version_id', ''), + self.uname_attr('release') + ] + version = '' + if best: + # This algorithm uses the last version in priority order that has + # the best precision. If the versions are not in conflict, that + # does not matter; otherwise, using the last one instead of the + # first one might be considered a surprise. + for v in versions: + if v.count(".") > version.count(".") or version == '': + version = v + else: + for v in versions: + if v != '': + version = v + break + if pretty and version and self.codename(): + version = u'{0} ({1})'.format(version, self.codename()) + return version + + def version_parts(self, best=False): + """ + Return the version of the OS distribution, as a tuple of version + numbers. + + For details, see :func:`distro.version_parts`. + """ + version_str = self.version(best=best) + if version_str: + version_regex = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?') + matches = version_regex.match(version_str) + if matches: + major, minor, build_number = matches.groups() + return major, minor or '', build_number or '' + return '', '', '' + + def major_version(self, best=False): + """ + Return the major version number of the current distribution. + + For details, see :func:`distro.major_version`. + """ + return self.version_parts(best)[0] + + def minor_version(self, best=False): + """ + Return the minor version number of the current distribution. + + For details, see :func:`distro.minor_version`. + """ + return self.version_parts(best)[1] + + def build_number(self, best=False): + """ + Return the build number of the current distribution. + + For details, see :func:`distro.build_number`. + """ + return self.version_parts(best)[2] + + def like(self): + """ + Return the IDs of distributions that are like the OS distribution. + + For details, see :func:`distro.like`. + """ + return self.os_release_attr('id_like') or '' + + def codename(self): + """ + Return the codename of the OS distribution. + + For details, see :func:`distro.codename`. + """ + return self.os_release_attr('codename') \ + or self.lsb_release_attr('codename') \ + or self.distro_release_attr('codename') \ + or '' + + def info(self, pretty=False, best=False): + """ + Return certain machine-readable information about the OS + distribution. + + For details, see :func:`distro.info`. + """ + return dict( + id=self.id(), + version=self.version(pretty, best), + version_parts=dict( + major=self.major_version(best), + minor=self.minor_version(best), + build_number=self.build_number(best) + ), + like=self.like(), + codename=self.codename(), + ) + + def os_release_info(self): + """ + Return a dictionary containing key-value pairs for the information + items from the os-release file data source of the OS distribution. + + For details, see :func:`distro.os_release_info`. + """ + return self._os_release_info + + def lsb_release_info(self): + """ + Return a dictionary containing key-value pairs for the information + items from the lsb_release command data source of the OS + distribution. + + For details, see :func:`distro.lsb_release_info`. + """ + return self._lsb_release_info + + def distro_release_info(self): + """ + Return a dictionary containing key-value pairs for the information + items from the distro release file data source of the OS + distribution. + + For details, see :func:`distro.distro_release_info`. + """ + return self._distro_release_info + + def uname_info(self): + """ + Return a dictionary containing key-value pairs for the information + items from the uname command data source of the OS distribution. + + For details, see :func:`distro.uname_info`. + """ + + def os_release_attr(self, attribute): + """ + Return a single named information item from the os-release file data + source of the OS distribution. + + For details, see :func:`distro.os_release_attr`. + """ + return self._os_release_info.get(attribute, '') + + def lsb_release_attr(self, attribute): + """ + Return a single named information item from the lsb_release command + output data source of the OS distribution. + + For details, see :func:`distro.lsb_release_attr`. + """ + return self._lsb_release_info.get(attribute, '') + + def distro_release_attr(self, attribute): + """ + Return a single named information item from the distro release file + data source of the OS distribution. + + For details, see :func:`distro.distro_release_attr`. + """ + return self._distro_release_info.get(attribute, '') + + def uname_attr(self, attribute): + """ + Return a single named information item from the uname command + output data source of the OS distribution. + + For details, see :func:`distro.uname_release_attr`. + """ + return self._uname_info.get(attribute, '') + + @cached_property + def _os_release_info(self): + """ + Get the information items from the specified os-release file. + + Returns: + A dictionary containing all information items. + """ + if os.path.isfile(self.os_release_file): + with open(self.os_release_file) as release_file: + return self._parse_os_release_content(release_file) + return {} + + @staticmethod + def _parse_os_release_content(lines): + """ + Parse the lines of an os-release file. + + Parameters: + + * lines: Iterable through the lines in the os-release file. + Each line must be a unicode string or a UTF-8 encoded byte + string. + + Returns: + A dictionary containing all information items. + """ + props = {} + 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. + # comments processed, quotes and backslash escape sequences + # processed, multi-line values assembled, trailing newlines + # stripped, etc.), so the tokens are now either: + # * variable assignments: var=value + # * commands or their arguments (not allowed in os-release) + if '=' in token: + k, v = token.split('=', 1) + if isinstance(v, bytes): + v = v.decode('utf-8') + props[k.lower()] = v + if k == 'VERSION': + # this handles cases in which the codename is in + # the `(CODENAME)` (rhel, centos, fedora) format + # or in the `, CODENAME` format (Ubuntu). + codename = re.search(r'(\(\D+\))|,(\s+)?\D+', v) + if codename: + codename = codename.group() + codename = codename.strip('()') + codename = codename.strip(',') + codename = codename.strip() + # codename appears within paranthese. + props['codename'] = codename + else: + props['codename'] = '' + else: + # Ignore any tokens that are not variable assignments + pass + return props + + @cached_property + def _lsb_release_info(self): + """ + Get the information items from the lsb_release command output. + + Returns: + A dictionary containing all information items. + """ + if not self.include_lsb: + return {} + with open(os.devnull, 'w') as devnull: + try: + cmd = ('lsb_release', '-a') + stdout = subprocess.check_output(cmd, stderr=devnull) + except OSError: # Command not found + return {} + content = stdout.decode(sys.getfilesystemencoding()).splitlines() + return self._parse_lsb_release_content(content) + + @staticmethod + def _parse_lsb_release_content(lines): + """ + Parse the output of the lsb_release command. + + Parameters: + + * lines: Iterable through the lines of the lsb_release output. + Each line must be a unicode string or a UTF-8 encoded byte + string. + + Returns: + A dictionary containing all information items. + """ + props = {} + for line in lines: + kv = line.strip('\n').split(':', 1) + if len(kv) != 2: + # Ignore lines without colon. + continue + k, v = kv + props.update({k.replace(' ', '_').lower(): v.strip()}) + return props + + @cached_property + def _uname_info(self): + with open(os.devnull, 'w') as devnull: + try: + cmd = ('uname', '-rs') + stdout = subprocess.check_output(cmd, stderr=devnull) + except OSError: + return {} + content = stdout.decode(sys.getfilesystemencoding()).splitlines() + return self._parse_uname_content(content) + + @staticmethod + def _parse_uname_content(lines): + props = {} + match = re.search(r'^([^\s]+)\s+([\d\.]+)', lines[0].strip()) + if match: + name, version = match.groups() + + # This is to prevent the Linux kernel version from + # appearing as the 'best' version on otherwise + # identifiable distributions. + if name == 'Linux': + return {} + props['id'] = name.lower() + props['name'] = name + props['release'] = version + return props + + @cached_property + def _distro_release_info(self): + """ + Get the information items from the specified distro release file. + + Returns: + A dictionary containing all information items. + """ + if self.distro_release_file: + # If it was specified, we use it and parse what we can, even if + # its file name or content does not match the expected pattern. + distro_info = self._parse_distro_release_file( + self.distro_release_file) + basename = os.path.basename(self.distro_release_file) + # The file name pattern for user-specified distro release files + # is somewhat more tolerant (compared to when searching for the + # file), because we want to use what was specified as best as + # possible. + match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) + if match: + distro_info['id'] = match.group(1) + return distro_info + else: + try: + basenames = os.listdir(_UNIXCONFDIR) + # 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. + basenames.sort() + except OSError: + # This may occur when /etc is not readable but we can't be + # 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'] + 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(_UNIXCONFDIR, 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) + return distro_info + return {} + + def _parse_distro_release_file(self, filepath): + """ + Parse a distro release file. + + Parameters: + + * filepath: Path name of the distro release file. + + Returns: + A dictionary containing all information items. + """ + try: + with open(filepath) 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): + # Ignore not being able to read a specific, seemingly version + # related file. + # See https://github.com/nir0s/distro/issues/162 + return {} + + @staticmethod + def _parse_distro_release_content(line): + """ + Parse a line from a distro release file. + + Parameters: + * line: Line from the distro release file. Must be a unicode string + or a UTF-8 encoded byte string. + + Returns: + A dictionary containing all information items. + """ + if isinstance(line, bytes): + line = line.decode('utf-8') + matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match( + line.strip()[::-1]) + distro_info = {} + if matches: + # regexp ensures non-None + distro_info['name'] = matches.group(3)[::-1] + if matches.group(2): + distro_info['version_id'] = matches.group(2)[::-1] + if matches.group(1): + distro_info['codename'] = matches.group(1)[::-1] + elif line: + distro_info['name'] = line.strip() + return distro_info + + +_distro = LinuxDistribution() + + +def main(): + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.StreamHandler(sys.stdout)) + + parser = argparse.ArgumentParser(description="OS distro info tool") + parser.add_argument( + '--json', + '-j', + help="Output in machine readable format", + action="store_true") + args = parser.parse_args() + + if args.json: + logger.info(json.dumps(info(), indent=4, sort_keys=True)) + else: + logger.info('Name: %s', name(pretty=True)) + distribution_version = version(pretty=True) + logger.info('Version: %s', distribution_version) + distribution_codename = codename() + logger.info('Codename: %s', distribution_codename) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/__init__.py new file mode 100644 index 0000000..0491234 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/__init__.py @@ -0,0 +1,35 @@ +""" +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.0.1" diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_ihatexml.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_ihatexml.py new file mode 100644 index 0000000..4c77717 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_ihatexml.py @@ -0,0 +1,288 @@ +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", 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", 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_inputstream.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_inputstream.py new file mode 100644 index 0000000..a65e55f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_inputstream.py @@ -0,0 +1,923 @@ +from __future__ import absolute_import, division, unicode_literals + +from pip._vendor.six import text_type, binary_type +from pip._vendor.six.moves import http_client, urllib + +import codecs +import re + +from pip._vendor import webencodings + +from .constants import EOF, spaceCharacters, asciiLetters, asciiUppercase +from .constants import _ReparseException +from . import _utils + +from io import StringIO + +try: + from io import BytesIO +except ImportError: + BytesIO = StringIO + +# 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 = set([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 None: + 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: # pylint:disable=bare-except + 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 overriden, we've been overriden + 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""" + p = self.position + data = self[p:p + len(bytes)] + rv = data.startswith(bytes) + 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""" + newPosition = self[self.position:].find(bytes) + if newPosition > -1: + # XXX: This is ugly, but I can't see a nicer way to fix this. + if self._position == -1: + self._position = 0 + self._position += (newPosition + len(bytes) - 1) + return True + else: + raise StopIteration + + +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): + 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 + 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, binary_type): + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_tokenizer.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_tokenizer.py new file mode 100644 index 0000000..178f6e7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_tokenizer.py @@ -0,0 +1,1721 @@ +from __future__ import absolute_import, division, unicode_literals + +from pip._vendor.six import unichr as chr + +from collections import deque + +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) + + +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 ¬i 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["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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/__init__.py new file mode 100644 index 0000000..a5ba4bf --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, division, unicode_literals + +from .py import Trie as PyTrie + +Trie = PyTrie + +# pylint:disable=wrong-import-position +try: + from .datrie import Trie as DATrie +except ImportError: + pass +else: + Trie = DATrie +# pylint:enable=wrong-import-position diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/_base.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/_base.py new file mode 100644 index 0000000..a1158bb --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/_base.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import, division, unicode_literals + +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/datrie.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/datrie.py new file mode 100644 index 0000000..e2e5f86 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/datrie.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, division, unicode_literals + +from datrie import Trie as DATrie +from pip._vendor.six import text_type + +from ._base import Trie as ABCTrie + + +class Trie(ABCTrie): + def __init__(self, data): + chars = set() + for key in data.keys(): + if not isinstance(key, text_type): + raise TypeError("All keys must be strings") + for char in key: + chars.add(char) + + self._data = DATrie("".join(chars)) + for key, value in data.items(): + self._data[key] = value + + def __contains__(self, key): + return key in self._data + + def __len__(self): + return len(self._data) + + def __iter__(self): + raise NotImplementedError() + + def __getitem__(self, key): + return self._data[key] + + def keys(self, prefix=None): + return self._data.keys(prefix) + + def has_keys_with_prefix(self, prefix): + return self._data.has_keys_with_prefix(prefix) + + def longest_prefix(self, prefix): + return self._data.longest_prefix(prefix) + + def longest_prefix_item(self, prefix): + return self._data.longest_prefix_item(prefix) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/py.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/py.py new file mode 100644 index 0000000..c178b21 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_trie/py.py @@ -0,0 +1,67 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_utils.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_utils.py new file mode 100644 index 0000000..0703afb --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/_utils.py @@ -0,0 +1,124 @@ +from __future__ import absolute_import, division, unicode_literals + +from types import ModuleType + +from pip._vendor.six import text_type + +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: # pylint:disable=bare-except + 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=()): + # Using _dictEntries instead of directly assigning to self is about + # twice as fast. Please do careful performance testing before changing + # anything here. + _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) + + +# 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/constants.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/constants.py new file mode 100644 index 0000000..1ff8041 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/constants.py @@ -0,0 +1,2947 @@ +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 = dict([((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 = dict([(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 = dict([(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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/alphabeticalattributes.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/alphabeticalattributes.py new file mode 100644 index 0000000..5ba926e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/alphabeticalattributes.py @@ -0,0 +1,29 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/base.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/base.py new file mode 100644 index 0000000..c7dbaed --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/base.py @@ -0,0 +1,12 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/inject_meta_charset.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/inject_meta_charset.py new file mode 100644 index 0000000..aefb5c8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/inject_meta_charset.py @@ -0,0 +1,73 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/lint.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/lint.py new file mode 100644 index 0000000..fcc07ee --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/lint.py @@ -0,0 +1,93 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/optionaltags.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/optionaltags.py new file mode 100644 index 0000000..4a86501 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/optionaltags.py @@ -0,0 +1,207 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/sanitizer.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/sanitizer.py new file mode 100644 index 0000000..af8e77b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/sanitizer.py @@ -0,0 +1,896 @@ +from __future__ import absolute_import, division, unicode_literals + +import re +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"] + + +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) + 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>') + # => <script> do_nasty_stuff() </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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/whitespace.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/whitespace.py new file mode 100644 index 0000000..0d12584 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/filters/whitespace.py @@ -0,0 +1,38 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/html5parser.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/html5parser.py new file mode 100644 index 0000000..ae41a13 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/html5parser.py @@ -0,0 +1,2791 @@ +from __future__ import absolute_import, division, unicode_literals +from pip._vendor.six import with_metaclass, viewkeys + +import types +from collections import OrderedDict + +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 = dict([(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.normalizedTokens(): + 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 normalizedTokens(self): + for token in self.tokenizer: + yield self.normalizeToken(token) + + 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 normalizeToken(self, token): + # HTML5 specific normalizations to the token stream + if token["type"] == tokenTypes["StartTag"]: + raw = token["data"] + token["data"] = OrderedDict(raw) + if len(raw) > len(token["data"]): + # we had some duplicated attribute, fix so first wins + token["data"].update(raw[::-1]) + + return token + + 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 = dict((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] + try: + info = {"type": type_names[token['type']]} + except: + raise + 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 + """ + + def __init__(self, parser, tree): + self.parser = parser + self.tree = tree + + 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): + return self.startTagHandler[token["name"]](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): + return self.endTagHandler[token["name"]](token) + + class InitialPhase(Phase): + 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): + # 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): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("head", self.startTagHead) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + (("head", "body", "html", "br"), self.endTagImplyHead) + ]) + self.endTagHandler.default = self.endTagOther + + 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"]}) + + class InHeadPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("title", self.startTagTitle), + (("noframes", "style"), self.startTagNoFramesStyle), + ("noscript", self.startTagNoscript), + ("script", self.startTagScript), + (("base", "basefont", "bgsound", "command", "link"), + self.startTagBaseLinkCommand), + ("meta", self.startTagMeta), + ("head", self.startTagHead) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("head", self.endTagHead), + (("br", "html", "body"), self.endTagHtmlBodyBr) + ]) + self.endTagHandler.default = self.endTagOther + + # 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")) + + class InHeadNoscriptPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + (("basefont", "bgsound", "link", "meta", "noframes", "style"), self.startTagBaseLinkCommand), + (("head", "noscript"), self.startTagHeadNoscript), + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("noscript", self.endTagNoscript), + ("br", self.endTagBr), + ]) + self.endTagHandler.default = self.endTagOther + + 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")) + + class AfterHeadPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("body", self.startTagBody), + ("frameset", self.startTagFrameset), + (("base", "basefont", "bgsound", "link", "meta", "noframes", "script", + "style", "title"), + self.startTagFromHead), + ("head", self.startTagHead) + ]) + self.startTagHandler.default = self.startTagOther + self.endTagHandler = _utils.MethodDispatcher([(("body", "html", "br"), + self.endTagHtmlBodyBr)]) + self.endTagHandler.default = self.endTagOther + + 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 + + class InBodyPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#parsing-main-inbody + # the really-really-really-very crazy mode + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + # Set this to the default handler + self.processSpaceCharacters = self.processSpaceCharactersNonPre + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + (("base", "basefont", "bgsound", "command", "link", "meta", + "script", "style", "title"), + self.startTagProcessInHead), + ("body", self.startTagBody), + ("frameset", self.startTagFrameset), + (("address", "article", "aside", "blockquote", "center", "details", + "dir", "div", "dl", "fieldset", "figcaption", "figure", + "footer", "header", "hgroup", "main", "menu", "nav", "ol", "p", + "section", "summary", "ul"), + self.startTagCloseP), + (headingElements, self.startTagHeading), + (("pre", "listing"), self.startTagPreListing), + ("form", self.startTagForm), + (("li", "dd", "dt"), self.startTagListItem), + ("plaintext", self.startTagPlaintext), + ("a", self.startTagA), + (("b", "big", "code", "em", "font", "i", "s", "small", "strike", + "strong", "tt", "u"), self.startTagFormatting), + ("nobr", self.startTagNobr), + ("button", self.startTagButton), + (("applet", "marquee", "object"), self.startTagAppletMarqueeObject), + ("xmp", self.startTagXmp), + ("table", self.startTagTable), + (("area", "br", "embed", "img", "keygen", "wbr"), + self.startTagVoidFormatting), + (("param", "source", "track"), self.startTagParamSource), + ("input", self.startTagInput), + ("hr", self.startTagHr), + ("image", self.startTagImage), + ("isindex", self.startTagIsIndex), + ("textarea", self.startTagTextarea), + ("iframe", self.startTagIFrame), + ("noscript", self.startTagNoscript), + (("noembed", "noframes"), self.startTagRawtext), + ("select", self.startTagSelect), + (("rp", "rt"), self.startTagRpRt), + (("option", "optgroup"), self.startTagOpt), + (("math"), self.startTagMath), + (("svg"), self.startTagSvg), + (("caption", "col", "colgroup", "frame", "head", + "tbody", "td", "tfoot", "th", "thead", + "tr"), self.startTagMisplaced) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("body", self.endTagBody), + ("html", self.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"), self.endTagBlock), + ("form", self.endTagForm), + ("p", self.endTagP), + (("dd", "dt", "li"), self.endTagListItem), + (headingElements, self.endTagHeading), + (("a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", + "strike", "strong", "tt", "u"), self.endTagFormatting), + (("applet", "marquee", "object"), self.endTagAppletMarqueeObject), + ("br", self.endTagBr), + ]) + self.endTagHandler.default = self.endTagOther + + 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 + + class TextPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + self.startTagHandler = _utils.MethodDispatcher([]) + self.startTagHandler.default = self.startTagOther + self.endTagHandler = _utils.MethodDispatcher([ + ("script", self.endTagScript)]) + self.endTagHandler.default = self.endTagOther + + 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 + + class InTablePhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#in-table + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("caption", self.startTagCaption), + ("colgroup", self.startTagColgroup), + ("col", self.startTagCol), + (("tbody", "tfoot", "thead"), self.startTagRowGroup), + (("td", "th", "tr"), self.startTagImplyTbody), + ("table", self.startTagTable), + (("style", "script"), self.startTagStyleScript), + ("input", self.startTagInput), + ("form", self.startTagForm) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("table", self.endTagTable), + (("body", "caption", "col", "colgroup", "html", "tbody", "td", + "tfoot", "th", "thead", "tr"), self.endTagIgnore) + ]) + self.endTagHandler.default = self.endTagOther + + # 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 + + class InTableTextPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + 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 + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th", + "thead", "tr"), self.startTagTableElement) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("caption", self.endTagCaption), + ("table", self.endTagTable), + (("body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", + "thead", "tr"), self.endTagIgnore) + ]) + self.endTagHandler.default = self.endTagOther + + 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) + + class InColumnGroupPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#in-column + + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("col", self.startTagCol) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("colgroup", self.endTagColgroup), + ("col", self.endTagCol) + ]) + self.endTagHandler.default = self.endTagOther + + 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 + + class InTableBodyPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#in-table0 + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("tr", self.startTagTr), + (("td", "th"), self.startTagTableCell), + (("caption", "col", "colgroup", "tbody", "tfoot", "thead"), + self.startTagTableOther) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + (("tbody", "tfoot", "thead"), self.endTagTableRowGroup), + ("table", self.endTagTable), + (("body", "caption", "col", "colgroup", "html", "td", "th", + "tr"), self.endTagIgnore) + ]) + self.endTagHandler.default = self.endTagOther + + # 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) + + class InRowPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#in-row + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + (("td", "th"), self.startTagTableCell), + (("caption", "col", "colgroup", "tbody", "tfoot", "thead", + "tr"), self.startTagTableOther) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("tr", self.endTagTr), + ("table", self.endTagTable), + (("tbody", "tfoot", "thead"), self.endTagTableRowGroup), + (("body", "caption", "col", "colgroup", "html", "td", "th"), + self.endTagIgnore) + ]) + self.endTagHandler.default = self.endTagOther + + # 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) + + class InCellPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#in-cell + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th", + "thead", "tr"), self.startTagTableOther) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + (("td", "th"), self.endTagTableCell), + (("body", "caption", "col", "colgroup", "html"), self.endTagIgnore), + (("table", "tbody", "tfoot", "thead", "tr"), self.endTagImply) + ]) + self.endTagHandler.default = self.endTagOther + + # 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) + + class InSelectPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("option", self.startTagOption), + ("optgroup", self.startTagOptgroup), + ("select", self.startTagSelect), + (("input", "keygen", "textarea"), self.startTagInput), + ("script", self.startTagScript) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("option", self.endTagOption), + ("optgroup", self.endTagOptgroup), + ("select", self.endTagSelect) + ]) + self.endTagHandler.default = self.endTagOther + + # 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"]}) + + class InSelectInTablePhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"), + self.startTagTable) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"), + self.endTagTable) + ]) + self.endTagHandler.default = self.endTagOther + + 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) + + class InForeignContentPhase(Phase): + 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 __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + 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()) & set(["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): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([("html", self.endTagHtml)]) + self.endTagHandler.default = self.endTagOther + + 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 + + class InFramesetPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#in-frameset + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("frameset", self.startTagFrameset), + ("frame", self.startTagFrame), + ("noframes", self.startTagNoframes) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("frameset", self.endTagFrameset) + ]) + self.endTagHandler.default = self.endTagOther + + 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"]}) + + class AfterFramesetPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#after3 + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("noframes", self.startTagNoframes) + ]) + self.startTagHandler.default = self.startTagOther + + self.endTagHandler = _utils.MethodDispatcher([ + ("html", self.endTagHtml) + ]) + self.endTagHandler.default = self.endTagOther + + 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"]}) + + class AfterAfterBodyPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml) + ]) + self.startTagHandler.default = self.startTagOther + + 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 + + class AfterAfterFramesetPhase(Phase): + def __init__(self, parser, tree): + Phase.__init__(self, parser, tree) + + self.startTagHandler = _utils.MethodDispatcher([ + ("html", self.startTagHtml), + ("noframes", self.startTagNoFrames) + ]) + self.startTagHandler.default = self.startTagOther + + 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"]}) + # 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'] = OrderedDict((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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/serializer.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/serializer.py new file mode 100644 index 0000000..53f4d44 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/serializer.py @@ -0,0 +1,409 @@ +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 < over < and similarly for &, >, 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 < > + & " ' 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 identifer 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("&", "&") + if self.escape_lt_in_attrs: + v = v.replace("<", "<") + 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("'", "'") + else: + v = v.replace('"', """) + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/__init__.py new file mode 100644 index 0000000..7ef5959 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/__init__.py @@ -0,0 +1,30 @@ +"""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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/genshi.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/genshi.py new file mode 100644 index 0000000..61d5fb6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/genshi.py @@ -0,0 +1,54 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/sax.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/sax.py new file mode 100644 index 0000000..f4ccea5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treeadapters/sax.py @@ -0,0 +1,50 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/__init__.py new file mode 100644 index 0000000..d44447e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/__init__.py @@ -0,0 +1,88 @@ +"""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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/base.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/base.py new file mode 100644 index 0000000..73973db --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/base.py @@ -0,0 +1,417 @@ +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 | set([(namespaces["html"], "button")])), False), + "list": (frozenset(scopingElements | set([(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 assocaited 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/dom.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/dom.py new file mode 100644 index 0000000..dcfac22 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/dom.py @@ -0,0 +1,236 @@ +from __future__ import absolute_import, division, unicode_literals + + +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree.py new file mode 100644 index 0000000..0dedf44 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree.py @@ -0,0 +1,340 @@ +from __future__ import absolute_import, division, unicode_literals +# pylint:disable=protected-access + +from pip._vendor.six import text_type + +import re + +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): + # Delete existing attributes first + # XXX - there may be a better way to do this... + for key in list(self._element.attrib.keys()): + del self._element.attrib[key] + for key, value in attributes.items(): + if isinstance(key, tuple): + name = "{%s}%s" % (key[2], key[1]) + else: + name = key + self._element.set(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) + for name, value in self.attributes.items(): + element.attributes[name] = value + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree_lxml.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree_lxml.py new file mode 100644 index 0000000..ca12a99 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treebuilders/etree_lxml.py @@ -0,0 +1,366 @@ +"""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 + +from . import base +from ..constants import DataLossWarning +from .. import constants +from . import etree as etree_builders +from .. import _ihatexml + +import lxml.etree as etree + + +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): + self._elementTree.getroot().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(dict): + def __init__(self, element, value=None): + if value is None: + value = {} + self._element = element + dict.__init__(self, value) # pylint:disable=non-parent-init-called + for key, value in self.items(): + if isinstance(key, tuple): + name = "{%s}%s" % (key[2], infosetFilter.coerceAttribute(key[1])) + else: + name = infosetFilter.coerceAttribute(key) + self._element._element.attrib[name] = value + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + if isinstance(key, tuple): + name = "{%s}%s" % (key[2], infosetFilter.coerceAttribute(key[1])) + else: + name = infosetFilter.coerceAttribute(key) + self._element._element.attrib[name] = value + + 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, attributes): + self._attributes = Attributes(self, attributes) + + attributes = property(_getAttributes, _setAttributes) + + def insertText(self, data, insertBefore=None): + data = infosetFilter.coerceCharacters(data) + builder.Element.insertText(self, data, insertBefore) + + def appendChild(self, child): + builder.Element.appendChild(self, child) + + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/__init__.py new file mode 100644 index 0000000..9bec207 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/__init__.py @@ -0,0 +1,154 @@ +"""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 do +implement a tree walker object (called TreeWalker by convention) that +implements a 'serialize' method taking a tree as sole argument and +returning an iterator generating 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/base.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/base.py new file mode 100644 index 0000000..80c474c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/base.py @@ -0,0 +1,252 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/dom.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/dom.py new file mode 100644 index 0000000..b0c89b0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/dom.py @@ -0,0 +1,43 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree.py new file mode 100644 index 0000000..95fc0c1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree.py @@ -0,0 +1,130 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree_lxml.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree_lxml.py new file mode 100644 index 0000000..e81ddf3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/etree_lxml.py @@ -0,0 +1,213 @@ +from __future__ import absolute_import, division, unicode_literals +from pip._vendor.six import text_type + +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 = {} + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/genshi.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/genshi.py new file mode 100644 index 0000000..7483be2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/html5lib/treewalkers/genshi.py @@ -0,0 +1,69 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/__init__.py new file mode 100644 index 0000000..847bf93 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/__init__.py @@ -0,0 +1,2 @@ +from .package_data import __version__ +from .core import * diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/codec.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/codec.py new file mode 100644 index 0000000..98c65ea --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/codec.py @@ -0,0 +1,118 @@ +from .core import encode, decode, alabel, ulabel, IDNAError +import codecs +import re + +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +class Codec(codecs.Codec): + + def encode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return "", 0 + + return encode(data), len(data) + + def decode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return u"", 0 + + return decode(data), len(data) + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return ("", 0) + + labels = _unicode_dots_re.split(data) + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return (u"", 0) + + # IDNA allows decoding to operate on Unicode strings, too. + if isinstance(data, unicode): + labels = _unicode_dots_re.split(data) + else: + # Must be ASCII string + data = str(data) + unicode(data, "ascii") + labels = data.split(".") + + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = u'.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = u'.' + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result = u".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + +class StreamReader(Codec, codecs.StreamReader): + pass + +def getregentry(): + return codecs.CodecInfo( + name='idna', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/compat.py new file mode 100644 index 0000000..4d47f33 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/compat.py @@ -0,0 +1,12 @@ +from .core import * +from .codec import * + +def ToASCII(label): + return encode(label) + +def ToUnicode(label): + return decode(label) + +def nameprep(s): + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/core.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/core.py new file mode 100644 index 0000000..104624a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/core.py @@ -0,0 +1,396 @@ +from . import idnadata +import bisect +import unicodedata +import re +import sys +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b'xn--' +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +if sys.version_info[0] == 3: + unicode = str + unichr = chr + +class IDNAError(UnicodeError): + """ Base exception for all IDNA-encoding related problems """ + pass + + +class IDNABidiError(IDNAError): + """ Exception when bidirectional requirements are not satisfied """ + pass + + +class InvalidCodepoint(IDNAError): + """ Exception when a disallowed or unallocated codepoint is used """ + pass + + +class InvalidCodepointContext(IDNAError): + """ Exception when the codepoint is not valid in the context it is used """ + pass + + +def _combining_class(cp): + v = unicodedata.combining(unichr(cp)) + if v == 0: + if not unicodedata.name(unichr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + +def _is_script(cp, script): + return intranges_contain(ord(cp), idnadata.scripts[script]) + +def _punycode(s): + return s.encode('punycode') + +def _unot(s): + return 'U+{0:04X}'.format(s) + + +def valid_label_length(label): + + if len(label) > 63: + return False + return True + + +def valid_string_length(label, trailing_dot): + + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label, check_ltr=False): + + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == '': + # String likely comes from a newer version of Unicode + raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) + if direction in ['R', 'AL', 'AN']: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ['R', 'AL']: + rtl = True + elif direction == 'L': + rtl = False + else: + raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) + + valid_ending = False + number_type = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) + # Bidi rule 3 + if direction in ['R', 'AL', 'EN', 'AN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + # Bidi rule 4 + if direction in ['AN', 'EN']: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError('Can not mix numeral types in a right-to-left label') + else: + # Bidi rule 5 + if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) + # Bidi rule 6 + if direction in ['L', 'EN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + + if not valid_ending: + raise IDNABidiError('Label ends with illegal codepoint directionality') + + return True + + +def check_initial_combiner(label): + + if unicodedata.category(label[0])[0] == 'M': + raise IDNAError('Label begins with an illegal combining character') + return True + + +def check_hyphen_ok(label): + + if label[2:4] == '--': + raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') + if label[0] == '-' or label[-1] == '-': + raise IDNAError('Label must not start or end with a hyphen') + return True + + +def check_nfc(label): + + if unicodedata.normalize('NFC', label) != label: + raise IDNAError('Label must be in Normalization Form C') + + +def valid_contextj(label, pos): + + cp_value = ord(label[pos]) + + if cp_value == 0x200c: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos-1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('L'), ord('D')]: + ok = True + break + + if not ok: + return False + + ok = False + for i in range(pos+1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('R'), ord('D')]: + ok = True + break + return ok + + if cp_value == 0x200d: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + + return False + + +def valid_contexto(label, pos, exception=False): + + cp_value = ord(label[pos]) + + if cp_value == 0x00b7: + if 0 < pos < len(label)-1: + if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label)-1 and len(label) > 1: + return _is_script(label[pos + 1], 'Greek') + return False + + elif cp_value == 0x05f3 or cp_value == 0x05f4: + if pos > 0: + return _is_script(label[pos - 1], 'Hebrew') + return False + + elif cp_value == 0x30fb: + for cp in label: + if cp == u'\u30fb': + continue + if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6f0 <= ord(cp) <= 0x06f9: + return False + return True + + elif 0x6f0 <= cp_value <= 0x6f9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + +def check_label(label): + + if isinstance(label, (bytes, bytearray)): + label = label.decode('utf-8') + if len(label) == 0: + raise IDNAError('Empty Label') + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for (pos, cp) in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + except ValueError: + raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): + if not valid_contexto(label, pos): + raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) + else: + raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + + check_bidi(label) + + +def alabel(label): + + try: + label = label.encode('ascii') + ulabel(label) + if not valid_label_length(label): + raise IDNAError('Label too long') + return label + except UnicodeEncodeError: + pass + + if not label: + raise IDNAError('No Input') + + label = unicode(label) + check_label(label) + label = _punycode(label) + label = _alabel_prefix + label + + if not valid_label_length(label): + raise IDNAError('Label too long') + + return label + + +def ulabel(label): + + if not isinstance(label, (bytes, bytearray)): + try: + label = label.encode('ascii') + except UnicodeEncodeError: + check_label(label) + return label + + label = label.lower() + if label.startswith(_alabel_prefix): + label = label[len(_alabel_prefix):] + else: + check_label(label) + return label.decode('ascii') + + label = label.decode('punycode') + check_label(label) + return label + + +def uts46_remap(domain, std3_rules=True, transitional=False): + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + output = u"" + try: + for pos, char in enumerate(domain): + code_point = ord(char) + uts46row = uts46data[code_point if code_point < 256 else + bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement = uts46row[2] if len(uts46row) == 3 else None + if (status == "V" or + (status == "D" and not transitional) or + (status == "3" and not std3_rules and replacement is None)): + output += char + elif replacement is not None and (status == "M" or + (status == "3" and not std3_rules) or + (status == "D" and transitional)): + output += replacement + elif status != "I": + raise IndexError() + return unicodedata.normalize("NFC", output) + except IndexError: + raise InvalidCodepoint( + "Codepoint {0} not allowed at position {1} in {2}".format( + _unot(code_point), pos + 1, repr(domain))) + + +def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split('.') + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if labels[-1] == '': + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(b'') + s = b'.'.join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError('Domain too long') + return s + + +def decode(s, strict=False, uts46=False, std3_rules=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(u'.') + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(u'') + return u'.'.join(result) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/idnadata.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/idnadata.py new file mode 100644 index 0000000..a80c959 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/idnadata.py @@ -0,0 +1,1979 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "11.0.0" +scripts = { + 'Greek': ( + 0x37000000374, + 0x37500000378, + 0x37a0000037e, + 0x37f00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038b, + 0x38c0000038d, + 0x38e000003a2, + 0x3a3000003e2, + 0x3f000000400, + 0x1d2600001d2b, + 0x1d5d00001d62, + 0x1d6600001d6b, + 0x1dbf00001dc0, + 0x1f0000001f16, + 0x1f1800001f1e, + 0x1f2000001f46, + 0x1f4800001f4e, + 0x1f5000001f58, + 0x1f5900001f5a, + 0x1f5b00001f5c, + 0x1f5d00001f5e, + 0x1f5f00001f7e, + 0x1f8000001fb5, + 0x1fb600001fc5, + 0x1fc600001fd4, + 0x1fd600001fdc, + 0x1fdd00001ff0, + 0x1ff200001ff5, + 0x1ff600001fff, + 0x212600002127, + 0xab650000ab66, + 0x101400001018f, + 0x101a0000101a1, + 0x1d2000001d246, + ), + 'Han': ( + 0x2e8000002e9a, + 0x2e9b00002ef4, + 0x2f0000002fd6, + 0x300500003006, + 0x300700003008, + 0x30210000302a, + 0x30380000303c, + 0x340000004db6, + 0x4e0000009ff0, + 0xf9000000fa6e, + 0xfa700000fada, + 0x200000002a6d7, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x2f8000002fa1e, + ), + 'Hebrew': ( + 0x591000005c8, + 0x5d0000005eb, + 0x5ef000005f5, + 0xfb1d0000fb37, + 0xfb380000fb3d, + 0xfb3e0000fb3f, + 0xfb400000fb42, + 0xfb430000fb45, + 0xfb460000fb50, + ), + 'Hiragana': ( + 0x304100003097, + 0x309d000030a0, + 0x1b0010001b11f, + 0x1f2000001f201, + ), + 'Katakana': ( + 0x30a1000030fb, + 0x30fd00003100, + 0x31f000003200, + 0x32d0000032ff, + 0x330000003358, + 0xff660000ff70, + 0xff710000ff9e, + 0x1b0000001b001, + ), +} +joining_types = { + 0x600: 85, + 0x601: 85, + 0x602: 85, + 0x603: 85, + 0x604: 85, + 0x605: 85, + 0x608: 85, + 0x60b: 85, + 0x620: 68, + 0x621: 85, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62a: 68, + 0x62b: 68, + 0x62c: 68, + 0x62d: 68, + 0x62e: 68, + 0x62f: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63a: 68, + 0x63b: 68, + 0x63c: 68, + 0x63d: 68, + 0x63e: 68, + 0x63f: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64a: 68, + 0x66e: 68, + 0x66f: 68, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x674: 85, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67a: 68, + 0x67b: 68, + 0x67c: 68, + 0x67d: 68, + 0x67e: 68, + 0x67f: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68a: 82, + 0x68b: 82, + 0x68c: 82, + 0x68d: 82, + 0x68e: 82, + 0x68f: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69a: 68, + 0x69b: 68, + 0x69c: 68, + 0x69d: 68, + 0x69e: 68, + 0x69f: 68, + 0x6a0: 68, + 0x6a1: 68, + 0x6a2: 68, + 0x6a3: 68, + 0x6a4: 68, + 0x6a5: 68, + 0x6a6: 68, + 0x6a7: 68, + 0x6a8: 68, + 0x6a9: 68, + 0x6aa: 68, + 0x6ab: 68, + 0x6ac: 68, + 0x6ad: 68, + 0x6ae: 68, + 0x6af: 68, + 0x6b0: 68, + 0x6b1: 68, + 0x6b2: 68, + 0x6b3: 68, + 0x6b4: 68, + 0x6b5: 68, + 0x6b6: 68, + 0x6b7: 68, + 0x6b8: 68, + 0x6b9: 68, + 0x6ba: 68, + 0x6bb: 68, + 0x6bc: 68, + 0x6bd: 68, + 0x6be: 68, + 0x6bf: 68, + 0x6c0: 82, + 0x6c1: 68, + 0x6c2: 68, + 0x6c3: 82, + 0x6c4: 82, + 0x6c5: 82, + 0x6c6: 82, + 0x6c7: 82, + 0x6c8: 82, + 0x6c9: 82, + 0x6ca: 82, + 0x6cb: 82, + 0x6cc: 68, + 0x6cd: 82, + 0x6ce: 68, + 0x6cf: 82, + 0x6d0: 68, + 0x6d1: 68, + 0x6d2: 82, + 0x6d3: 82, + 0x6d5: 82, + 0x6dd: 85, + 0x6ee: 82, + 0x6ef: 82, + 0x6fa: 68, + 0x6fb: 68, + 0x6fc: 68, + 0x6ff: 68, + 0x70f: 84, + 0x710: 82, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71a: 68, + 0x71b: 68, + 0x71c: 68, + 0x71d: 68, + 0x71e: 82, + 0x71f: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72a: 82, + 0x72b: 68, + 0x72c: 82, + 0x72d: 68, + 0x72e: 68, + 0x72f: 82, + 0x74d: 82, + 0x74e: 68, + 0x74f: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75a: 82, + 0x75b: 82, + 0x75c: 68, + 0x75d: 68, + 0x75e: 68, + 0x75f: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76a: 68, + 0x76b: 82, + 0x76c: 82, + 0x76d: 68, + 0x76e: 68, + 0x76f: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77a: 68, + 0x77b: 68, + 0x77c: 68, + 0x77d: 68, + 0x77e: 68, + 0x77f: 68, + 0x7ca: 68, + 0x7cb: 68, + 0x7cc: 68, + 0x7cd: 68, + 0x7ce: 68, + 0x7cf: 68, + 0x7d0: 68, + 0x7d1: 68, + 0x7d2: 68, + 0x7d3: 68, + 0x7d4: 68, + 0x7d5: 68, + 0x7d6: 68, + 0x7d7: 68, + 0x7d8: 68, + 0x7d9: 68, + 0x7da: 68, + 0x7db: 68, + 0x7dc: 68, + 0x7dd: 68, + 0x7de: 68, + 0x7df: 68, + 0x7e0: 68, + 0x7e1: 68, + 0x7e2: 68, + 0x7e3: 68, + 0x7e4: 68, + 0x7e5: 68, + 0x7e6: 68, + 0x7e7: 68, + 0x7e8: 68, + 0x7e9: 68, + 0x7ea: 68, + 0x7fa: 67, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84a: 68, + 0x84b: 68, + 0x84c: 68, + 0x84d: 68, + 0x84e: 68, + 0x84f: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 85, + 0x857: 85, + 0x858: 85, + 0x860: 68, + 0x861: 85, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x866: 85, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86a: 82, + 0x8a0: 68, + 0x8a1: 68, + 0x8a2: 68, + 0x8a3: 68, + 0x8a4: 68, + 0x8a5: 68, + 0x8a6: 68, + 0x8a7: 68, + 0x8a8: 68, + 0x8a9: 68, + 0x8aa: 82, + 0x8ab: 82, + 0x8ac: 82, + 0x8ad: 85, + 0x8ae: 82, + 0x8af: 68, + 0x8b0: 68, + 0x8b1: 82, + 0x8b2: 82, + 0x8b3: 68, + 0x8b4: 68, + 0x8b6: 68, + 0x8b7: 68, + 0x8b8: 68, + 0x8b9: 82, + 0x8ba: 68, + 0x8bb: 68, + 0x8bc: 68, + 0x8bd: 68, + 0x8e2: 85, + 0x1806: 85, + 0x1807: 68, + 0x180a: 67, + 0x180e: 85, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182a: 68, + 0x182b: 68, + 0x182c: 68, + 0x182d: 68, + 0x182e: 68, + 0x182f: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183a: 68, + 0x183b: 68, + 0x183c: 68, + 0x183d: 68, + 0x183e: 68, + 0x183f: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184a: 68, + 0x184b: 68, + 0x184c: 68, + 0x184d: 68, + 0x184e: 68, + 0x184f: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185a: 68, + 0x185b: 68, + 0x185c: 68, + 0x185d: 68, + 0x185e: 68, + 0x185f: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186a: 68, + 0x186b: 68, + 0x186c: 68, + 0x186d: 68, + 0x186e: 68, + 0x186f: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1880: 85, + 0x1881: 85, + 0x1882: 85, + 0x1883: 85, + 0x1884: 85, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188a: 68, + 0x188b: 68, + 0x188c: 68, + 0x188d: 68, + 0x188e: 68, + 0x188f: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189a: 68, + 0x189b: 68, + 0x189c: 68, + 0x189d: 68, + 0x189e: 68, + 0x189f: 68, + 0x18a0: 68, + 0x18a1: 68, + 0x18a2: 68, + 0x18a3: 68, + 0x18a4: 68, + 0x18a5: 68, + 0x18a6: 68, + 0x18a7: 68, + 0x18a8: 68, + 0x18aa: 68, + 0x200c: 85, + 0x200d: 67, + 0x202f: 85, + 0x2066: 85, + 0x2067: 85, + 0x2068: 85, + 0x2069: 85, + 0xa840: 68, + 0xa841: 68, + 0xa842: 68, + 0xa843: 68, + 0xa844: 68, + 0xa845: 68, + 0xa846: 68, + 0xa847: 68, + 0xa848: 68, + 0xa849: 68, + 0xa84a: 68, + 0xa84b: 68, + 0xa84c: 68, + 0xa84d: 68, + 0xa84e: 68, + 0xa84f: 68, + 0xa850: 68, + 0xa851: 68, + 0xa852: 68, + 0xa853: 68, + 0xa854: 68, + 0xa855: 68, + 0xa856: 68, + 0xa857: 68, + 0xa858: 68, + 0xa859: 68, + 0xa85a: 68, + 0xa85b: 68, + 0xa85c: 68, + 0xa85d: 68, + 0xa85e: 68, + 0xa85f: 68, + 0xa860: 68, + 0xa861: 68, + 0xa862: 68, + 0xa863: 68, + 0xa864: 68, + 0xa865: 68, + 0xa866: 68, + 0xa867: 68, + 0xa868: 68, + 0xa869: 68, + 0xa86a: 68, + 0xa86b: 68, + 0xa86c: 68, + 0xa86d: 68, + 0xa86e: 68, + 0xa86f: 68, + 0xa870: 68, + 0xa871: 68, + 0xa872: 76, + 0xa873: 85, + 0x10ac0: 68, + 0x10ac1: 68, + 0x10ac2: 68, + 0x10ac3: 68, + 0x10ac4: 68, + 0x10ac5: 82, + 0x10ac6: 85, + 0x10ac7: 82, + 0x10ac8: 85, + 0x10ac9: 82, + 0x10aca: 82, + 0x10acb: 85, + 0x10acc: 85, + 0x10acd: 76, + 0x10ace: 82, + 0x10acf: 82, + 0x10ad0: 82, + 0x10ad1: 82, + 0x10ad2: 82, + 0x10ad3: 68, + 0x10ad4: 68, + 0x10ad5: 68, + 0x10ad6: 68, + 0x10ad7: 76, + 0x10ad8: 68, + 0x10ad9: 68, + 0x10ada: 68, + 0x10adb: 68, + 0x10adc: 68, + 0x10add: 82, + 0x10ade: 68, + 0x10adf: 68, + 0x10ae0: 68, + 0x10ae1: 82, + 0x10ae2: 85, + 0x10ae3: 85, + 0x10ae4: 82, + 0x10aeb: 68, + 0x10aec: 68, + 0x10aed: 68, + 0x10aee: 68, + 0x10aef: 82, + 0x10b80: 68, + 0x10b81: 82, + 0x10b82: 68, + 0x10b83: 82, + 0x10b84: 82, + 0x10b85: 82, + 0x10b86: 68, + 0x10b87: 68, + 0x10b88: 68, + 0x10b89: 82, + 0x10b8a: 68, + 0x10b8b: 68, + 0x10b8c: 82, + 0x10b8d: 68, + 0x10b8e: 82, + 0x10b8f: 82, + 0x10b90: 68, + 0x10b91: 82, + 0x10ba9: 82, + 0x10baa: 82, + 0x10bab: 82, + 0x10bac: 82, + 0x10bad: 68, + 0x10bae: 68, + 0x10baf: 85, + 0x10d00: 76, + 0x10d01: 68, + 0x10d02: 68, + 0x10d03: 68, + 0x10d04: 68, + 0x10d05: 68, + 0x10d06: 68, + 0x10d07: 68, + 0x10d08: 68, + 0x10d09: 68, + 0x10d0a: 68, + 0x10d0b: 68, + 0x10d0c: 68, + 0x10d0d: 68, + 0x10d0e: 68, + 0x10d0f: 68, + 0x10d10: 68, + 0x10d11: 68, + 0x10d12: 68, + 0x10d13: 68, + 0x10d14: 68, + 0x10d15: 68, + 0x10d16: 68, + 0x10d17: 68, + 0x10d18: 68, + 0x10d19: 68, + 0x10d1a: 68, + 0x10d1b: 68, + 0x10d1c: 68, + 0x10d1d: 68, + 0x10d1e: 68, + 0x10d1f: 68, + 0x10d20: 68, + 0x10d21: 68, + 0x10d22: 82, + 0x10d23: 68, + 0x10f30: 68, + 0x10f31: 68, + 0x10f32: 68, + 0x10f33: 82, + 0x10f34: 68, + 0x10f35: 68, + 0x10f36: 68, + 0x10f37: 68, + 0x10f38: 68, + 0x10f39: 68, + 0x10f3a: 68, + 0x10f3b: 68, + 0x10f3c: 68, + 0x10f3d: 68, + 0x10f3e: 68, + 0x10f3f: 68, + 0x10f40: 68, + 0x10f41: 68, + 0x10f42: 68, + 0x10f43: 68, + 0x10f44: 68, + 0x10f45: 85, + 0x10f51: 68, + 0x10f52: 68, + 0x10f53: 68, + 0x10f54: 82, + 0x110bd: 85, + 0x110cd: 85, + 0x1e900: 68, + 0x1e901: 68, + 0x1e902: 68, + 0x1e903: 68, + 0x1e904: 68, + 0x1e905: 68, + 0x1e906: 68, + 0x1e907: 68, + 0x1e908: 68, + 0x1e909: 68, + 0x1e90a: 68, + 0x1e90b: 68, + 0x1e90c: 68, + 0x1e90d: 68, + 0x1e90e: 68, + 0x1e90f: 68, + 0x1e910: 68, + 0x1e911: 68, + 0x1e912: 68, + 0x1e913: 68, + 0x1e914: 68, + 0x1e915: 68, + 0x1e916: 68, + 0x1e917: 68, + 0x1e918: 68, + 0x1e919: 68, + 0x1e91a: 68, + 0x1e91b: 68, + 0x1e91c: 68, + 0x1e91d: 68, + 0x1e91e: 68, + 0x1e91f: 68, + 0x1e920: 68, + 0x1e921: 68, + 0x1e922: 68, + 0x1e923: 68, + 0x1e924: 68, + 0x1e925: 68, + 0x1e926: 68, + 0x1e927: 68, + 0x1e928: 68, + 0x1e929: 68, + 0x1e92a: 68, + 0x1e92b: 68, + 0x1e92c: 68, + 0x1e92d: 68, + 0x1e92e: 68, + 0x1e92f: 68, + 0x1e930: 68, + 0x1e931: 68, + 0x1e932: 68, + 0x1e933: 68, + 0x1e934: 68, + 0x1e935: 68, + 0x1e936: 68, + 0x1e937: 68, + 0x1e938: 68, + 0x1e939: 68, + 0x1e93a: 68, + 0x1e93b: 68, + 0x1e93c: 68, + 0x1e93d: 68, + 0x1e93e: 68, + 0x1e93f: 68, + 0x1e940: 68, + 0x1e941: 68, + 0x1e942: 68, + 0x1e943: 68, +} +codepoint_classes = { + 'PVALID': ( + 0x2d0000002e, + 0x300000003a, + 0x610000007b, + 0xdf000000f7, + 0xf800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010a, + 0x10b0000010c, + 0x10d0000010e, + 0x10f00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011a, + 0x11b0000011c, + 0x11d0000011e, + 0x11f00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012a, + 0x12b0000012c, + 0x12d0000012e, + 0x12f00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13a0000013b, + 0x13c0000013d, + 0x13e0000013f, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14b0000014c, + 0x14d0000014e, + 0x14f00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015a, + 0x15b0000015c, + 0x15d0000015e, + 0x15f00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016a, + 0x16b0000016c, + 0x16d0000016e, + 0x16f00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17a0000017b, + 0x17c0000017d, + 0x17e0000017f, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18c0000018e, + 0x19200000193, + 0x19500000196, + 0x1990000019c, + 0x19e0000019f, + 0x1a1000001a2, + 0x1a3000001a4, + 0x1a5000001a6, + 0x1a8000001a9, + 0x1aa000001ac, + 0x1ad000001ae, + 0x1b0000001b1, + 0x1b4000001b5, + 0x1b6000001b7, + 0x1b9000001bc, + 0x1bd000001c4, + 0x1ce000001cf, + 0x1d0000001d1, + 0x1d2000001d3, + 0x1d4000001d5, + 0x1d6000001d7, + 0x1d8000001d9, + 0x1da000001db, + 0x1dc000001de, + 0x1df000001e0, + 0x1e1000001e2, + 0x1e3000001e4, + 0x1e5000001e6, + 0x1e7000001e8, + 0x1e9000001ea, + 0x1eb000001ec, + 0x1ed000001ee, + 0x1ef000001f1, + 0x1f5000001f6, + 0x1f9000001fa, + 0x1fb000001fc, + 0x1fd000001fe, + 0x1ff00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020a, + 0x20b0000020c, + 0x20d0000020e, + 0x20f00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021a, + 0x21b0000021c, + 0x21d0000021e, + 0x21f00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022a, + 0x22b0000022c, + 0x22d0000022e, + 0x22f00000230, + 0x23100000232, + 0x2330000023a, + 0x23c0000023d, + 0x23f00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024a, + 0x24b0000024c, + 0x24d0000024e, + 0x24f000002b0, + 0x2b9000002c2, + 0x2c6000002d2, + 0x2ec000002ed, + 0x2ee000002ef, + 0x30000000340, + 0x34200000343, + 0x3460000034f, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37b0000037e, + 0x39000000391, + 0x3ac000003cf, + 0x3d7000003d8, + 0x3d9000003da, + 0x3db000003dc, + 0x3dd000003de, + 0x3df000003e0, + 0x3e1000003e2, + 0x3e3000003e4, + 0x3e5000003e6, + 0x3e7000003e8, + 0x3e9000003ea, + 0x3eb000003ec, + 0x3ed000003ee, + 0x3ef000003f0, + 0x3f3000003f4, + 0x3f8000003f9, + 0x3fb000003fd, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046a, + 0x46b0000046c, + 0x46d0000046e, + 0x46f00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047a, + 0x47b0000047c, + 0x47d0000047e, + 0x47f00000480, + 0x48100000482, + 0x48300000488, + 0x48b0000048c, + 0x48d0000048e, + 0x48f00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049a, + 0x49b0000049c, + 0x49d0000049e, + 0x49f000004a0, + 0x4a1000004a2, + 0x4a3000004a4, + 0x4a5000004a6, + 0x4a7000004a8, + 0x4a9000004aa, + 0x4ab000004ac, + 0x4ad000004ae, + 0x4af000004b0, + 0x4b1000004b2, + 0x4b3000004b4, + 0x4b5000004b6, + 0x4b7000004b8, + 0x4b9000004ba, + 0x4bb000004bc, + 0x4bd000004be, + 0x4bf000004c0, + 0x4c2000004c3, + 0x4c4000004c5, + 0x4c6000004c7, + 0x4c8000004c9, + 0x4ca000004cb, + 0x4cc000004cd, + 0x4ce000004d0, + 0x4d1000004d2, + 0x4d3000004d4, + 0x4d5000004d6, + 0x4d7000004d8, + 0x4d9000004da, + 0x4db000004dc, + 0x4dd000004de, + 0x4df000004e0, + 0x4e1000004e2, + 0x4e3000004e4, + 0x4e5000004e6, + 0x4e7000004e8, + 0x4e9000004ea, + 0x4eb000004ec, + 0x4ed000004ee, + 0x4ef000004f0, + 0x4f1000004f2, + 0x4f3000004f4, + 0x4f5000004f6, + 0x4f7000004f8, + 0x4f9000004fa, + 0x4fb000004fc, + 0x4fd000004fe, + 0x4ff00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050a, + 0x50b0000050c, + 0x50d0000050e, + 0x50f00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051a, + 0x51b0000051c, + 0x51d0000051e, + 0x51f00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052a, + 0x52b0000052c, + 0x52d0000052e, + 0x52f00000530, + 0x5590000055a, + 0x56000000587, + 0x58800000589, + 0x591000005be, + 0x5bf000005c0, + 0x5c1000005c3, + 0x5c4000005c6, + 0x5c7000005c8, + 0x5d0000005eb, + 0x5ef000005f3, + 0x6100000061b, + 0x62000000640, + 0x64100000660, + 0x66e00000675, + 0x679000006d4, + 0x6d5000006dd, + 0x6df000006e9, + 0x6ea000006f0, + 0x6fa00000700, + 0x7100000074b, + 0x74d000007b2, + 0x7c0000007f6, + 0x7fd000007fe, + 0x8000000082e, + 0x8400000085c, + 0x8600000086b, + 0x8a0000008b5, + 0x8b6000008be, + 0x8d3000008e2, + 0x8e300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098d, + 0x98f00000991, + 0x993000009a9, + 0x9aa000009b1, + 0x9b2000009b3, + 0x9b6000009ba, + 0x9bc000009c5, + 0x9c7000009c9, + 0x9cb000009cf, + 0x9d7000009d8, + 0x9e0000009e4, + 0x9e6000009f2, + 0x9fc000009fd, + 0x9fe000009ff, + 0xa0100000a04, + 0xa0500000a0b, + 0xa0f00000a11, + 0xa1300000a29, + 0xa2a00000a31, + 0xa3200000a33, + 0xa3500000a36, + 0xa3800000a3a, + 0xa3c00000a3d, + 0xa3e00000a43, + 0xa4700000a49, + 0xa4b00000a4e, + 0xa5100000a52, + 0xa5c00000a5d, + 0xa6600000a76, + 0xa8100000a84, + 0xa8500000a8e, + 0xa8f00000a92, + 0xa9300000aa9, + 0xaaa00000ab1, + 0xab200000ab4, + 0xab500000aba, + 0xabc00000ac6, + 0xac700000aca, + 0xacb00000ace, + 0xad000000ad1, + 0xae000000ae4, + 0xae600000af0, + 0xaf900000b00, + 0xb0100000b04, + 0xb0500000b0d, + 0xb0f00000b11, + 0xb1300000b29, + 0xb2a00000b31, + 0xb3200000b34, + 0xb3500000b3a, + 0xb3c00000b45, + 0xb4700000b49, + 0xb4b00000b4e, + 0xb5600000b58, + 0xb5f00000b64, + 0xb6600000b70, + 0xb7100000b72, + 0xb8200000b84, + 0xb8500000b8b, + 0xb8e00000b91, + 0xb9200000b96, + 0xb9900000b9b, + 0xb9c00000b9d, + 0xb9e00000ba0, + 0xba300000ba5, + 0xba800000bab, + 0xbae00000bba, + 0xbbe00000bc3, + 0xbc600000bc9, + 0xbca00000bce, + 0xbd000000bd1, + 0xbd700000bd8, + 0xbe600000bf0, + 0xc0000000c0d, + 0xc0e00000c11, + 0xc1200000c29, + 0xc2a00000c3a, + 0xc3d00000c45, + 0xc4600000c49, + 0xc4a00000c4e, + 0xc5500000c57, + 0xc5800000c5b, + 0xc6000000c64, + 0xc6600000c70, + 0xc8000000c84, + 0xc8500000c8d, + 0xc8e00000c91, + 0xc9200000ca9, + 0xcaa00000cb4, + 0xcb500000cba, + 0xcbc00000cc5, + 0xcc600000cc9, + 0xcca00000cce, + 0xcd500000cd7, + 0xcde00000cdf, + 0xce000000ce4, + 0xce600000cf0, + 0xcf100000cf3, + 0xd0000000d04, + 0xd0500000d0d, + 0xd0e00000d11, + 0xd1200000d45, + 0xd4600000d49, + 0xd4a00000d4f, + 0xd5400000d58, + 0xd5f00000d64, + 0xd6600000d70, + 0xd7a00000d80, + 0xd8200000d84, + 0xd8500000d97, + 0xd9a00000db2, + 0xdb300000dbc, + 0xdbd00000dbe, + 0xdc000000dc7, + 0xdca00000dcb, + 0xdcf00000dd5, + 0xdd600000dd7, + 0xdd800000de0, + 0xde600000df0, + 0xdf200000df4, + 0xe0100000e33, + 0xe3400000e3b, + 0xe4000000e4f, + 0xe5000000e5a, + 0xe8100000e83, + 0xe8400000e85, + 0xe8700000e89, + 0xe8a00000e8b, + 0xe8d00000e8e, + 0xe9400000e98, + 0xe9900000ea0, + 0xea100000ea4, + 0xea500000ea6, + 0xea700000ea8, + 0xeaa00000eac, + 0xead00000eb3, + 0xeb400000eba, + 0xebb00000ebe, + 0xec000000ec5, + 0xec600000ec7, + 0xec800000ece, + 0xed000000eda, + 0xede00000ee0, + 0xf0000000f01, + 0xf0b00000f0c, + 0xf1800000f1a, + 0xf2000000f2a, + 0xf3500000f36, + 0xf3700000f38, + 0xf3900000f3a, + 0xf3e00000f43, + 0xf4400000f48, + 0xf4900000f4d, + 0xf4e00000f52, + 0xf5300000f57, + 0xf5800000f5c, + 0xf5d00000f69, + 0xf6a00000f6d, + 0xf7100000f73, + 0xf7400000f75, + 0xf7a00000f81, + 0xf8200000f85, + 0xf8600000f93, + 0xf9400000f98, + 0xf9900000f9d, + 0xf9e00000fa2, + 0xfa300000fa7, + 0xfa800000fac, + 0xfad00000fb9, + 0xfba00000fbd, + 0xfc600000fc7, + 0x10000000104a, + 0x10500000109e, + 0x10d0000010fb, + 0x10fd00001100, + 0x120000001249, + 0x124a0000124e, + 0x125000001257, + 0x125800001259, + 0x125a0000125e, + 0x126000001289, + 0x128a0000128e, + 0x1290000012b1, + 0x12b2000012b6, + 0x12b8000012bf, + 0x12c0000012c1, + 0x12c2000012c6, + 0x12c8000012d7, + 0x12d800001311, + 0x131200001316, + 0x13180000135b, + 0x135d00001360, + 0x138000001390, + 0x13a0000013f6, + 0x14010000166d, + 0x166f00001680, + 0x16810000169b, + 0x16a0000016eb, + 0x16f1000016f9, + 0x17000000170d, + 0x170e00001715, + 0x172000001735, + 0x174000001754, + 0x17600000176d, + 0x176e00001771, + 0x177200001774, + 0x1780000017b4, + 0x17b6000017d4, + 0x17d7000017d8, + 0x17dc000017de, + 0x17e0000017ea, + 0x18100000181a, + 0x182000001879, + 0x1880000018ab, + 0x18b0000018f6, + 0x19000000191f, + 0x19200000192c, + 0x19300000193c, + 0x19460000196e, + 0x197000001975, + 0x1980000019ac, + 0x19b0000019ca, + 0x19d0000019da, + 0x1a0000001a1c, + 0x1a2000001a5f, + 0x1a6000001a7d, + 0x1a7f00001a8a, + 0x1a9000001a9a, + 0x1aa700001aa8, + 0x1ab000001abe, + 0x1b0000001b4c, + 0x1b5000001b5a, + 0x1b6b00001b74, + 0x1b8000001bf4, + 0x1c0000001c38, + 0x1c4000001c4a, + 0x1c4d00001c7e, + 0x1cd000001cd3, + 0x1cd400001cfa, + 0x1d0000001d2c, + 0x1d2f00001d30, + 0x1d3b00001d3c, + 0x1d4e00001d4f, + 0x1d6b00001d78, + 0x1d7900001d9b, + 0x1dc000001dfa, + 0x1dfb00001e00, + 0x1e0100001e02, + 0x1e0300001e04, + 0x1e0500001e06, + 0x1e0700001e08, + 0x1e0900001e0a, + 0x1e0b00001e0c, + 0x1e0d00001e0e, + 0x1e0f00001e10, + 0x1e1100001e12, + 0x1e1300001e14, + 0x1e1500001e16, + 0x1e1700001e18, + 0x1e1900001e1a, + 0x1e1b00001e1c, + 0x1e1d00001e1e, + 0x1e1f00001e20, + 0x1e2100001e22, + 0x1e2300001e24, + 0x1e2500001e26, + 0x1e2700001e28, + 0x1e2900001e2a, + 0x1e2b00001e2c, + 0x1e2d00001e2e, + 0x1e2f00001e30, + 0x1e3100001e32, + 0x1e3300001e34, + 0x1e3500001e36, + 0x1e3700001e38, + 0x1e3900001e3a, + 0x1e3b00001e3c, + 0x1e3d00001e3e, + 0x1e3f00001e40, + 0x1e4100001e42, + 0x1e4300001e44, + 0x1e4500001e46, + 0x1e4700001e48, + 0x1e4900001e4a, + 0x1e4b00001e4c, + 0x1e4d00001e4e, + 0x1e4f00001e50, + 0x1e5100001e52, + 0x1e5300001e54, + 0x1e5500001e56, + 0x1e5700001e58, + 0x1e5900001e5a, + 0x1e5b00001e5c, + 0x1e5d00001e5e, + 0x1e5f00001e60, + 0x1e6100001e62, + 0x1e6300001e64, + 0x1e6500001e66, + 0x1e6700001e68, + 0x1e6900001e6a, + 0x1e6b00001e6c, + 0x1e6d00001e6e, + 0x1e6f00001e70, + 0x1e7100001e72, + 0x1e7300001e74, + 0x1e7500001e76, + 0x1e7700001e78, + 0x1e7900001e7a, + 0x1e7b00001e7c, + 0x1e7d00001e7e, + 0x1e7f00001e80, + 0x1e8100001e82, + 0x1e8300001e84, + 0x1e8500001e86, + 0x1e8700001e88, + 0x1e8900001e8a, + 0x1e8b00001e8c, + 0x1e8d00001e8e, + 0x1e8f00001e90, + 0x1e9100001e92, + 0x1e9300001e94, + 0x1e9500001e9a, + 0x1e9c00001e9e, + 0x1e9f00001ea0, + 0x1ea100001ea2, + 0x1ea300001ea4, + 0x1ea500001ea6, + 0x1ea700001ea8, + 0x1ea900001eaa, + 0x1eab00001eac, + 0x1ead00001eae, + 0x1eaf00001eb0, + 0x1eb100001eb2, + 0x1eb300001eb4, + 0x1eb500001eb6, + 0x1eb700001eb8, + 0x1eb900001eba, + 0x1ebb00001ebc, + 0x1ebd00001ebe, + 0x1ebf00001ec0, + 0x1ec100001ec2, + 0x1ec300001ec4, + 0x1ec500001ec6, + 0x1ec700001ec8, + 0x1ec900001eca, + 0x1ecb00001ecc, + 0x1ecd00001ece, + 0x1ecf00001ed0, + 0x1ed100001ed2, + 0x1ed300001ed4, + 0x1ed500001ed6, + 0x1ed700001ed8, + 0x1ed900001eda, + 0x1edb00001edc, + 0x1edd00001ede, + 0x1edf00001ee0, + 0x1ee100001ee2, + 0x1ee300001ee4, + 0x1ee500001ee6, + 0x1ee700001ee8, + 0x1ee900001eea, + 0x1eeb00001eec, + 0x1eed00001eee, + 0x1eef00001ef0, + 0x1ef100001ef2, + 0x1ef300001ef4, + 0x1ef500001ef6, + 0x1ef700001ef8, + 0x1ef900001efa, + 0x1efb00001efc, + 0x1efd00001efe, + 0x1eff00001f08, + 0x1f1000001f16, + 0x1f2000001f28, + 0x1f3000001f38, + 0x1f4000001f46, + 0x1f5000001f58, + 0x1f6000001f68, + 0x1f7000001f71, + 0x1f7200001f73, + 0x1f7400001f75, + 0x1f7600001f77, + 0x1f7800001f79, + 0x1f7a00001f7b, + 0x1f7c00001f7d, + 0x1fb000001fb2, + 0x1fb600001fb7, + 0x1fc600001fc7, + 0x1fd000001fd3, + 0x1fd600001fd8, + 0x1fe000001fe3, + 0x1fe400001fe8, + 0x1ff600001ff7, + 0x214e0000214f, + 0x218400002185, + 0x2c3000002c5f, + 0x2c6100002c62, + 0x2c6500002c67, + 0x2c6800002c69, + 0x2c6a00002c6b, + 0x2c6c00002c6d, + 0x2c7100002c72, + 0x2c7300002c75, + 0x2c7600002c7c, + 0x2c8100002c82, + 0x2c8300002c84, + 0x2c8500002c86, + 0x2c8700002c88, + 0x2c8900002c8a, + 0x2c8b00002c8c, + 0x2c8d00002c8e, + 0x2c8f00002c90, + 0x2c9100002c92, + 0x2c9300002c94, + 0x2c9500002c96, + 0x2c9700002c98, + 0x2c9900002c9a, + 0x2c9b00002c9c, + 0x2c9d00002c9e, + 0x2c9f00002ca0, + 0x2ca100002ca2, + 0x2ca300002ca4, + 0x2ca500002ca6, + 0x2ca700002ca8, + 0x2ca900002caa, + 0x2cab00002cac, + 0x2cad00002cae, + 0x2caf00002cb0, + 0x2cb100002cb2, + 0x2cb300002cb4, + 0x2cb500002cb6, + 0x2cb700002cb8, + 0x2cb900002cba, + 0x2cbb00002cbc, + 0x2cbd00002cbe, + 0x2cbf00002cc0, + 0x2cc100002cc2, + 0x2cc300002cc4, + 0x2cc500002cc6, + 0x2cc700002cc8, + 0x2cc900002cca, + 0x2ccb00002ccc, + 0x2ccd00002cce, + 0x2ccf00002cd0, + 0x2cd100002cd2, + 0x2cd300002cd4, + 0x2cd500002cd6, + 0x2cd700002cd8, + 0x2cd900002cda, + 0x2cdb00002cdc, + 0x2cdd00002cde, + 0x2cdf00002ce0, + 0x2ce100002ce2, + 0x2ce300002ce5, + 0x2cec00002ced, + 0x2cee00002cf2, + 0x2cf300002cf4, + 0x2d0000002d26, + 0x2d2700002d28, + 0x2d2d00002d2e, + 0x2d3000002d68, + 0x2d7f00002d97, + 0x2da000002da7, + 0x2da800002daf, + 0x2db000002db7, + 0x2db800002dbf, + 0x2dc000002dc7, + 0x2dc800002dcf, + 0x2dd000002dd7, + 0x2dd800002ddf, + 0x2de000002e00, + 0x2e2f00002e30, + 0x300500003008, + 0x302a0000302e, + 0x303c0000303d, + 0x304100003097, + 0x30990000309b, + 0x309d0000309f, + 0x30a1000030fb, + 0x30fc000030ff, + 0x310500003130, + 0x31a0000031bb, + 0x31f000003200, + 0x340000004db6, + 0x4e0000009ff0, + 0xa0000000a48d, + 0xa4d00000a4fe, + 0xa5000000a60d, + 0xa6100000a62c, + 0xa6410000a642, + 0xa6430000a644, + 0xa6450000a646, + 0xa6470000a648, + 0xa6490000a64a, + 0xa64b0000a64c, + 0xa64d0000a64e, + 0xa64f0000a650, + 0xa6510000a652, + 0xa6530000a654, + 0xa6550000a656, + 0xa6570000a658, + 0xa6590000a65a, + 0xa65b0000a65c, + 0xa65d0000a65e, + 0xa65f0000a660, + 0xa6610000a662, + 0xa6630000a664, + 0xa6650000a666, + 0xa6670000a668, + 0xa6690000a66a, + 0xa66b0000a66c, + 0xa66d0000a670, + 0xa6740000a67e, + 0xa67f0000a680, + 0xa6810000a682, + 0xa6830000a684, + 0xa6850000a686, + 0xa6870000a688, + 0xa6890000a68a, + 0xa68b0000a68c, + 0xa68d0000a68e, + 0xa68f0000a690, + 0xa6910000a692, + 0xa6930000a694, + 0xa6950000a696, + 0xa6970000a698, + 0xa6990000a69a, + 0xa69b0000a69c, + 0xa69e0000a6e6, + 0xa6f00000a6f2, + 0xa7170000a720, + 0xa7230000a724, + 0xa7250000a726, + 0xa7270000a728, + 0xa7290000a72a, + 0xa72b0000a72c, + 0xa72d0000a72e, + 0xa72f0000a732, + 0xa7330000a734, + 0xa7350000a736, + 0xa7370000a738, + 0xa7390000a73a, + 0xa73b0000a73c, + 0xa73d0000a73e, + 0xa73f0000a740, + 0xa7410000a742, + 0xa7430000a744, + 0xa7450000a746, + 0xa7470000a748, + 0xa7490000a74a, + 0xa74b0000a74c, + 0xa74d0000a74e, + 0xa74f0000a750, + 0xa7510000a752, + 0xa7530000a754, + 0xa7550000a756, + 0xa7570000a758, + 0xa7590000a75a, + 0xa75b0000a75c, + 0xa75d0000a75e, + 0xa75f0000a760, + 0xa7610000a762, + 0xa7630000a764, + 0xa7650000a766, + 0xa7670000a768, + 0xa7690000a76a, + 0xa76b0000a76c, + 0xa76d0000a76e, + 0xa76f0000a770, + 0xa7710000a779, + 0xa77a0000a77b, + 0xa77c0000a77d, + 0xa77f0000a780, + 0xa7810000a782, + 0xa7830000a784, + 0xa7850000a786, + 0xa7870000a789, + 0xa78c0000a78d, + 0xa78e0000a790, + 0xa7910000a792, + 0xa7930000a796, + 0xa7970000a798, + 0xa7990000a79a, + 0xa79b0000a79c, + 0xa79d0000a79e, + 0xa79f0000a7a0, + 0xa7a10000a7a2, + 0xa7a30000a7a4, + 0xa7a50000a7a6, + 0xa7a70000a7a8, + 0xa7a90000a7aa, + 0xa7af0000a7b0, + 0xa7b50000a7b6, + 0xa7b70000a7b8, + 0xa7b90000a7ba, + 0xa7f70000a7f8, + 0xa7fa0000a828, + 0xa8400000a874, + 0xa8800000a8c6, + 0xa8d00000a8da, + 0xa8e00000a8f8, + 0xa8fb0000a8fc, + 0xa8fd0000a92e, + 0xa9300000a954, + 0xa9800000a9c1, + 0xa9cf0000a9da, + 0xa9e00000a9ff, + 0xaa000000aa37, + 0xaa400000aa4e, + 0xaa500000aa5a, + 0xaa600000aa77, + 0xaa7a0000aac3, + 0xaadb0000aade, + 0xaae00000aaf0, + 0xaaf20000aaf7, + 0xab010000ab07, + 0xab090000ab0f, + 0xab110000ab17, + 0xab200000ab27, + 0xab280000ab2f, + 0xab300000ab5b, + 0xab600000ab66, + 0xabc00000abeb, + 0xabec0000abee, + 0xabf00000abfa, + 0xac000000d7a4, + 0xfa0e0000fa10, + 0xfa110000fa12, + 0xfa130000fa15, + 0xfa1f0000fa20, + 0xfa210000fa22, + 0xfa230000fa25, + 0xfa270000fa2a, + 0xfb1e0000fb1f, + 0xfe200000fe30, + 0xfe730000fe74, + 0x100000001000c, + 0x1000d00010027, + 0x100280001003b, + 0x1003c0001003e, + 0x1003f0001004e, + 0x100500001005e, + 0x10080000100fb, + 0x101fd000101fe, + 0x102800001029d, + 0x102a0000102d1, + 0x102e0000102e1, + 0x1030000010320, + 0x1032d00010341, + 0x103420001034a, + 0x103500001037b, + 0x103800001039e, + 0x103a0000103c4, + 0x103c8000103d0, + 0x104280001049e, + 0x104a0000104aa, + 0x104d8000104fc, + 0x1050000010528, + 0x1053000010564, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1080000010806, + 0x1080800010809, + 0x1080a00010836, + 0x1083700010839, + 0x1083c0001083d, + 0x1083f00010856, + 0x1086000010877, + 0x108800001089f, + 0x108e0000108f3, + 0x108f4000108f6, + 0x1090000010916, + 0x109200001093a, + 0x10980000109b8, + 0x109be000109c0, + 0x10a0000010a04, + 0x10a0500010a07, + 0x10a0c00010a14, + 0x10a1500010a18, + 0x10a1900010a36, + 0x10a3800010a3b, + 0x10a3f00010a40, + 0x10a6000010a7d, + 0x10a8000010a9d, + 0x10ac000010ac8, + 0x10ac900010ae7, + 0x10b0000010b36, + 0x10b4000010b56, + 0x10b6000010b73, + 0x10b8000010b92, + 0x10c0000010c49, + 0x10cc000010cf3, + 0x10d0000010d28, + 0x10d3000010d3a, + 0x10f0000010f1d, + 0x10f2700010f28, + 0x10f3000010f51, + 0x1100000011047, + 0x1106600011070, + 0x1107f000110bb, + 0x110d0000110e9, + 0x110f0000110fa, + 0x1110000011135, + 0x1113600011140, + 0x1114400011147, + 0x1115000011174, + 0x1117600011177, + 0x11180000111c5, + 0x111c9000111cd, + 0x111d0000111db, + 0x111dc000111dd, + 0x1120000011212, + 0x1121300011238, + 0x1123e0001123f, + 0x1128000011287, + 0x1128800011289, + 0x1128a0001128e, + 0x1128f0001129e, + 0x1129f000112a9, + 0x112b0000112eb, + 0x112f0000112fa, + 0x1130000011304, + 0x113050001130d, + 0x1130f00011311, + 0x1131300011329, + 0x1132a00011331, + 0x1133200011334, + 0x113350001133a, + 0x1133b00011345, + 0x1134700011349, + 0x1134b0001134e, + 0x1135000011351, + 0x1135700011358, + 0x1135d00011364, + 0x113660001136d, + 0x1137000011375, + 0x114000001144b, + 0x114500001145a, + 0x1145e0001145f, + 0x11480000114c6, + 0x114c7000114c8, + 0x114d0000114da, + 0x11580000115b6, + 0x115b8000115c1, + 0x115d8000115de, + 0x1160000011641, + 0x1164400011645, + 0x116500001165a, + 0x11680000116b8, + 0x116c0000116ca, + 0x117000001171b, + 0x1171d0001172c, + 0x117300001173a, + 0x118000001183b, + 0x118c0000118ea, + 0x118ff00011900, + 0x11a0000011a3f, + 0x11a4700011a48, + 0x11a5000011a84, + 0x11a8600011a9a, + 0x11a9d00011a9e, + 0x11ac000011af9, + 0x11c0000011c09, + 0x11c0a00011c37, + 0x11c3800011c41, + 0x11c5000011c5a, + 0x11c7200011c90, + 0x11c9200011ca8, + 0x11ca900011cb7, + 0x11d0000011d07, + 0x11d0800011d0a, + 0x11d0b00011d37, + 0x11d3a00011d3b, + 0x11d3c00011d3e, + 0x11d3f00011d48, + 0x11d5000011d5a, + 0x11d6000011d66, + 0x11d6700011d69, + 0x11d6a00011d8f, + 0x11d9000011d92, + 0x11d9300011d99, + 0x11da000011daa, + 0x11ee000011ef7, + 0x120000001239a, + 0x1248000012544, + 0x130000001342f, + 0x1440000014647, + 0x1680000016a39, + 0x16a4000016a5f, + 0x16a6000016a6a, + 0x16ad000016aee, + 0x16af000016af5, + 0x16b0000016b37, + 0x16b4000016b44, + 0x16b5000016b5a, + 0x16b6300016b78, + 0x16b7d00016b90, + 0x16e6000016e80, + 0x16f0000016f45, + 0x16f5000016f7f, + 0x16f8f00016fa0, + 0x16fe000016fe2, + 0x17000000187f2, + 0x1880000018af3, + 0x1b0000001b11f, + 0x1b1700001b2fc, + 0x1bc000001bc6b, + 0x1bc700001bc7d, + 0x1bc800001bc89, + 0x1bc900001bc9a, + 0x1bc9d0001bc9f, + 0x1da000001da37, + 0x1da3b0001da6d, + 0x1da750001da76, + 0x1da840001da85, + 0x1da9b0001daa0, + 0x1daa10001dab0, + 0x1e0000001e007, + 0x1e0080001e019, + 0x1e01b0001e022, + 0x1e0230001e025, + 0x1e0260001e02b, + 0x1e8000001e8c5, + 0x1e8d00001e8d7, + 0x1e9220001e94b, + 0x1e9500001e95a, + 0x200000002a6d7, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + ), + 'CONTEXTJ': ( + 0x200c0000200e, + ), + 'CONTEXTO': ( + 0xb7000000b8, + 0x37500000376, + 0x5f3000005f5, + 0x6600000066a, + 0x6f0000006fa, + 0x30fb000030fc, + ), +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/intranges.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/intranges.py new file mode 100644 index 0000000..fa8a735 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/intranges.py @@ -0,0 +1,53 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect + +def intranges_from_list(list_): + """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. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i+1 < len(sorted_list): + if sorted_list[i] == sorted_list[i+1]-1: + continue + current_range = sorted_list[last_write+1:i+1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + +def _encode_range(start, end): + return (start << 32) | end + +def _decode_range(r): + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_, ranges): + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos-1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/package_data.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/package_data.py new file mode 100644 index 0000000..257e898 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/package_data.py @@ -0,0 +1,2 @@ +__version__ = '2.8' + diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/uts46data.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/uts46data.py new file mode 100644 index 0000000..a68ed4c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/idna/uts46data.py @@ -0,0 +1,8205 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "11.0.0" +def _seg_0(): + return [ + (0x0, '3'), + (0x1, '3'), + (0x2, '3'), + (0x3, '3'), + (0x4, '3'), + (0x5, '3'), + (0x6, '3'), + (0x7, '3'), + (0x8, '3'), + (0x9, '3'), + (0xA, '3'), + (0xB, '3'), + (0xC, '3'), + (0xD, '3'), + (0xE, '3'), + (0xF, '3'), + (0x10, '3'), + (0x11, '3'), + (0x12, '3'), + (0x13, '3'), + (0x14, '3'), + (0x15, '3'), + (0x16, '3'), + (0x17, '3'), + (0x18, '3'), + (0x19, '3'), + (0x1A, '3'), + (0x1B, '3'), + (0x1C, '3'), + (0x1D, '3'), + (0x1E, '3'), + (0x1F, '3'), + (0x20, '3'), + (0x21, '3'), + (0x22, '3'), + (0x23, '3'), + (0x24, '3'), + (0x25, '3'), + (0x26, '3'), + (0x27, '3'), + (0x28, '3'), + (0x29, '3'), + (0x2A, '3'), + (0x2B, '3'), + (0x2C, '3'), + (0x2D, 'V'), + (0x2E, 'V'), + (0x2F, '3'), + (0x30, 'V'), + (0x31, 'V'), + (0x32, 'V'), + (0x33, 'V'), + (0x34, 'V'), + (0x35, 'V'), + (0x36, 'V'), + (0x37, 'V'), + (0x38, 'V'), + (0x39, 'V'), + (0x3A, '3'), + (0x3B, '3'), + (0x3C, '3'), + (0x3D, '3'), + (0x3E, '3'), + (0x3F, '3'), + (0x40, '3'), + (0x41, 'M', u'a'), + (0x42, 'M', u'b'), + (0x43, 'M', u'c'), + (0x44, 'M', u'd'), + (0x45, 'M', u'e'), + (0x46, 'M', u'f'), + (0x47, 'M', u'g'), + (0x48, 'M', u'h'), + (0x49, 'M', u'i'), + (0x4A, 'M', u'j'), + (0x4B, 'M', u'k'), + (0x4C, 'M', u'l'), + (0x4D, 'M', u'm'), + (0x4E, 'M', u'n'), + (0x4F, 'M', u'o'), + (0x50, 'M', u'p'), + (0x51, 'M', u'q'), + (0x52, 'M', u'r'), + (0x53, 'M', u's'), + (0x54, 'M', u't'), + (0x55, 'M', u'u'), + (0x56, 'M', u'v'), + (0x57, 'M', u'w'), + (0x58, 'M', u'x'), + (0x59, 'M', u'y'), + (0x5A, 'M', u'z'), + (0x5B, '3'), + (0x5C, '3'), + (0x5D, '3'), + (0x5E, '3'), + (0x5F, '3'), + (0x60, '3'), + (0x61, 'V'), + (0x62, 'V'), + (0x63, 'V'), + ] + +def _seg_1(): + return [ + (0x64, 'V'), + (0x65, 'V'), + (0x66, 'V'), + (0x67, 'V'), + (0x68, 'V'), + (0x69, 'V'), + (0x6A, 'V'), + (0x6B, 'V'), + (0x6C, 'V'), + (0x6D, 'V'), + (0x6E, 'V'), + (0x6F, 'V'), + (0x70, 'V'), + (0x71, 'V'), + (0x72, 'V'), + (0x73, 'V'), + (0x74, 'V'), + (0x75, 'V'), + (0x76, 'V'), + (0x77, 'V'), + (0x78, 'V'), + (0x79, 'V'), + (0x7A, 'V'), + (0x7B, '3'), + (0x7C, '3'), + (0x7D, '3'), + (0x7E, '3'), + (0x7F, '3'), + (0x80, 'X'), + (0x81, 'X'), + (0x82, 'X'), + (0x83, 'X'), + (0x84, 'X'), + (0x85, 'X'), + (0x86, 'X'), + (0x87, 'X'), + (0x88, 'X'), + (0x89, 'X'), + (0x8A, 'X'), + (0x8B, 'X'), + (0x8C, 'X'), + (0x8D, 'X'), + (0x8E, 'X'), + (0x8F, 'X'), + (0x90, 'X'), + (0x91, 'X'), + (0x92, 'X'), + (0x93, 'X'), + (0x94, 'X'), + (0x95, 'X'), + (0x96, 'X'), + (0x97, 'X'), + (0x98, 'X'), + (0x99, 'X'), + (0x9A, 'X'), + (0x9B, 'X'), + (0x9C, 'X'), + (0x9D, 'X'), + (0x9E, 'X'), + (0x9F, 'X'), + (0xA0, '3', u' '), + (0xA1, 'V'), + (0xA2, 'V'), + (0xA3, 'V'), + (0xA4, 'V'), + (0xA5, 'V'), + (0xA6, 'V'), + (0xA7, 'V'), + (0xA8, '3', u' ̈'), + (0xA9, 'V'), + (0xAA, 'M', u'a'), + (0xAB, 'V'), + (0xAC, 'V'), + (0xAD, 'I'), + (0xAE, 'V'), + (0xAF, '3', u' ̄'), + (0xB0, 'V'), + (0xB1, 'V'), + (0xB2, 'M', u'2'), + (0xB3, 'M', u'3'), + (0xB4, '3', u' ́'), + (0xB5, 'M', u'μ'), + (0xB6, 'V'), + (0xB7, 'V'), + (0xB8, '3', u' ̧'), + (0xB9, 'M', u'1'), + (0xBA, 'M', u'o'), + (0xBB, 'V'), + (0xBC, 'M', u'1⁄4'), + (0xBD, 'M', u'1⁄2'), + (0xBE, 'M', u'3⁄4'), + (0xBF, 'V'), + (0xC0, 'M', u'à'), + (0xC1, 'M', u'á'), + (0xC2, 'M', u'â'), + (0xC3, 'M', u'ã'), + (0xC4, 'M', u'ä'), + (0xC5, 'M', u'å'), + (0xC6, 'M', u'æ'), + (0xC7, 'M', u'ç'), + ] + +def _seg_2(): + return [ + (0xC8, 'M', u'è'), + (0xC9, 'M', u'é'), + (0xCA, 'M', u'ê'), + (0xCB, 'M', u'ë'), + (0xCC, 'M', u'ì'), + (0xCD, 'M', u'í'), + (0xCE, 'M', u'î'), + (0xCF, 'M', u'ï'), + (0xD0, 'M', u'ð'), + (0xD1, 'M', u'ñ'), + (0xD2, 'M', u'ò'), + (0xD3, 'M', u'ó'), + (0xD4, 'M', u'ô'), + (0xD5, 'M', u'õ'), + (0xD6, 'M', u'ö'), + (0xD7, 'V'), + (0xD8, 'M', u'ø'), + (0xD9, 'M', u'ù'), + (0xDA, 'M', u'ú'), + (0xDB, 'M', u'û'), + (0xDC, 'M', u'ü'), + (0xDD, 'M', u'ý'), + (0xDE, 'M', u'þ'), + (0xDF, 'D', u'ss'), + (0xE0, 'V'), + (0xE1, 'V'), + (0xE2, 'V'), + (0xE3, 'V'), + (0xE4, 'V'), + (0xE5, 'V'), + (0xE6, 'V'), + (0xE7, 'V'), + (0xE8, 'V'), + (0xE9, 'V'), + (0xEA, 'V'), + (0xEB, 'V'), + (0xEC, 'V'), + (0xED, 'V'), + (0xEE, 'V'), + (0xEF, 'V'), + (0xF0, 'V'), + (0xF1, 'V'), + (0xF2, 'V'), + (0xF3, 'V'), + (0xF4, 'V'), + (0xF5, 'V'), + (0xF6, 'V'), + (0xF7, 'V'), + (0xF8, 'V'), + (0xF9, 'V'), + (0xFA, 'V'), + (0xFB, 'V'), + (0xFC, 'V'), + (0xFD, 'V'), + (0xFE, 'V'), + (0xFF, 'V'), + (0x100, 'M', u'ā'), + (0x101, 'V'), + (0x102, 'M', u'ă'), + (0x103, 'V'), + (0x104, 'M', u'ą'), + (0x105, 'V'), + (0x106, 'M', u'ć'), + (0x107, 'V'), + (0x108, 'M', u'ĉ'), + (0x109, 'V'), + (0x10A, 'M', u'ċ'), + (0x10B, 'V'), + (0x10C, 'M', u'č'), + (0x10D, 'V'), + (0x10E, 'M', u'ď'), + (0x10F, 'V'), + (0x110, 'M', u'đ'), + (0x111, 'V'), + (0x112, 'M', u'ē'), + (0x113, 'V'), + (0x114, 'M', u'ĕ'), + (0x115, 'V'), + (0x116, 'M', u'ė'), + (0x117, 'V'), + (0x118, 'M', u'ę'), + (0x119, 'V'), + (0x11A, 'M', u'ě'), + (0x11B, 'V'), + (0x11C, 'M', u'ĝ'), + (0x11D, 'V'), + (0x11E, 'M', u'ğ'), + (0x11F, 'V'), + (0x120, 'M', u'ġ'), + (0x121, 'V'), + (0x122, 'M', u'ģ'), + (0x123, 'V'), + (0x124, 'M', u'ĥ'), + (0x125, 'V'), + (0x126, 'M', u'ħ'), + (0x127, 'V'), + (0x128, 'M', u'ĩ'), + (0x129, 'V'), + (0x12A, 'M', u'ī'), + (0x12B, 'V'), + ] + +def _seg_3(): + return [ + (0x12C, 'M', u'ĭ'), + (0x12D, 'V'), + (0x12E, 'M', u'į'), + (0x12F, 'V'), + (0x130, 'M', u'i̇'), + (0x131, 'V'), + (0x132, 'M', u'ij'), + (0x134, 'M', u'ĵ'), + (0x135, 'V'), + (0x136, 'M', u'ķ'), + (0x137, 'V'), + (0x139, 'M', u'ĺ'), + (0x13A, 'V'), + (0x13B, 'M', u'ļ'), + (0x13C, 'V'), + (0x13D, 'M', u'ľ'), + (0x13E, 'V'), + (0x13F, 'M', u'l·'), + (0x141, 'M', u'ł'), + (0x142, 'V'), + (0x143, 'M', u'ń'), + (0x144, 'V'), + (0x145, 'M', u'ņ'), + (0x146, 'V'), + (0x147, 'M', u'ň'), + (0x148, 'V'), + (0x149, 'M', u'ʼn'), + (0x14A, 'M', u'ŋ'), + (0x14B, 'V'), + (0x14C, 'M', u'ō'), + (0x14D, 'V'), + (0x14E, 'M', u'ŏ'), + (0x14F, 'V'), + (0x150, 'M', u'ő'), + (0x151, 'V'), + (0x152, 'M', u'œ'), + (0x153, 'V'), + (0x154, 'M', u'ŕ'), + (0x155, 'V'), + (0x156, 'M', u'ŗ'), + (0x157, 'V'), + (0x158, 'M', u'ř'), + (0x159, 'V'), + (0x15A, 'M', u'ś'), + (0x15B, 'V'), + (0x15C, 'M', u'ŝ'), + (0x15D, 'V'), + (0x15E, 'M', u'ş'), + (0x15F, 'V'), + (0x160, 'M', u'š'), + (0x161, 'V'), + (0x162, 'M', u'ţ'), + (0x163, 'V'), + (0x164, 'M', u'ť'), + (0x165, 'V'), + (0x166, 'M', u'ŧ'), + (0x167, 'V'), + (0x168, 'M', u'ũ'), + (0x169, 'V'), + (0x16A, 'M', u'ū'), + (0x16B, 'V'), + (0x16C, 'M', u'ŭ'), + (0x16D, 'V'), + (0x16E, 'M', u'ů'), + (0x16F, 'V'), + (0x170, 'M', u'ű'), + (0x171, 'V'), + (0x172, 'M', u'ų'), + (0x173, 'V'), + (0x174, 'M', u'ŵ'), + (0x175, 'V'), + (0x176, 'M', u'ŷ'), + (0x177, 'V'), + (0x178, 'M', u'ÿ'), + (0x179, 'M', u'ź'), + (0x17A, 'V'), + (0x17B, 'M', u'ż'), + (0x17C, 'V'), + (0x17D, 'M', u'ž'), + (0x17E, 'V'), + (0x17F, 'M', u's'), + (0x180, 'V'), + (0x181, 'M', u'ɓ'), + (0x182, 'M', u'ƃ'), + (0x183, 'V'), + (0x184, 'M', u'ƅ'), + (0x185, 'V'), + (0x186, 'M', u'ɔ'), + (0x187, 'M', u'ƈ'), + (0x188, 'V'), + (0x189, 'M', u'ɖ'), + (0x18A, 'M', u'ɗ'), + (0x18B, 'M', u'ƌ'), + (0x18C, 'V'), + (0x18E, 'M', u'ǝ'), + (0x18F, 'M', u'ə'), + (0x190, 'M', u'ɛ'), + (0x191, 'M', u'ƒ'), + (0x192, 'V'), + (0x193, 'M', u'ɠ'), + ] + +def _seg_4(): + return [ + (0x194, 'M', u'ɣ'), + (0x195, 'V'), + (0x196, 'M', u'ɩ'), + (0x197, 'M', u'ɨ'), + (0x198, 'M', u'ƙ'), + (0x199, 'V'), + (0x19C, 'M', u'ɯ'), + (0x19D, 'M', u'ɲ'), + (0x19E, 'V'), + (0x19F, 'M', u'ɵ'), + (0x1A0, 'M', u'ơ'), + (0x1A1, 'V'), + (0x1A2, 'M', u'ƣ'), + (0x1A3, 'V'), + (0x1A4, 'M', u'ƥ'), + (0x1A5, 'V'), + (0x1A6, 'M', u'ʀ'), + (0x1A7, 'M', u'ƨ'), + (0x1A8, 'V'), + (0x1A9, 'M', u'ʃ'), + (0x1AA, 'V'), + (0x1AC, 'M', u'ƭ'), + (0x1AD, 'V'), + (0x1AE, 'M', u'ʈ'), + (0x1AF, 'M', u'ư'), + (0x1B0, 'V'), + (0x1B1, 'M', u'ʊ'), + (0x1B2, 'M', u'ʋ'), + (0x1B3, 'M', u'ƴ'), + (0x1B4, 'V'), + (0x1B5, 'M', u'ƶ'), + (0x1B6, 'V'), + (0x1B7, 'M', u'ʒ'), + (0x1B8, 'M', u'ƹ'), + (0x1B9, 'V'), + (0x1BC, 'M', u'ƽ'), + (0x1BD, 'V'), + (0x1C4, 'M', u'dž'), + (0x1C7, 'M', u'lj'), + (0x1CA, 'M', u'nj'), + (0x1CD, 'M', u'ǎ'), + (0x1CE, 'V'), + (0x1CF, 'M', u'ǐ'), + (0x1D0, 'V'), + (0x1D1, 'M', u'ǒ'), + (0x1D2, 'V'), + (0x1D3, 'M', u'ǔ'), + (0x1D4, 'V'), + (0x1D5, 'M', u'ǖ'), + (0x1D6, 'V'), + (0x1D7, 'M', u'ǘ'), + (0x1D8, 'V'), + (0x1D9, 'M', u'ǚ'), + (0x1DA, 'V'), + (0x1DB, 'M', u'ǜ'), + (0x1DC, 'V'), + (0x1DE, 'M', u'ǟ'), + (0x1DF, 'V'), + (0x1E0, 'M', u'ǡ'), + (0x1E1, 'V'), + (0x1E2, 'M', u'ǣ'), + (0x1E3, 'V'), + (0x1E4, 'M', u'ǥ'), + (0x1E5, 'V'), + (0x1E6, 'M', u'ǧ'), + (0x1E7, 'V'), + (0x1E8, 'M', u'ǩ'), + (0x1E9, 'V'), + (0x1EA, 'M', u'ǫ'), + (0x1EB, 'V'), + (0x1EC, 'M', u'ǭ'), + (0x1ED, 'V'), + (0x1EE, 'M', u'ǯ'), + (0x1EF, 'V'), + (0x1F1, 'M', u'dz'), + (0x1F4, 'M', u'ǵ'), + (0x1F5, 'V'), + (0x1F6, 'M', u'ƕ'), + (0x1F7, 'M', u'ƿ'), + (0x1F8, 'M', u'ǹ'), + (0x1F9, 'V'), + (0x1FA, 'M', u'ǻ'), + (0x1FB, 'V'), + (0x1FC, 'M', u'ǽ'), + (0x1FD, 'V'), + (0x1FE, 'M', u'ǿ'), + (0x1FF, 'V'), + (0x200, 'M', u'ȁ'), + (0x201, 'V'), + (0x202, 'M', u'ȃ'), + (0x203, 'V'), + (0x204, 'M', u'ȅ'), + (0x205, 'V'), + (0x206, 'M', u'ȇ'), + (0x207, 'V'), + (0x208, 'M', u'ȉ'), + (0x209, 'V'), + (0x20A, 'M', u'ȋ'), + (0x20B, 'V'), + (0x20C, 'M', u'ȍ'), + ] + +def _seg_5(): + return [ + (0x20D, 'V'), + (0x20E, 'M', u'ȏ'), + (0x20F, 'V'), + (0x210, 'M', u'ȑ'), + (0x211, 'V'), + (0x212, 'M', u'ȓ'), + (0x213, 'V'), + (0x214, 'M', u'ȕ'), + (0x215, 'V'), + (0x216, 'M', u'ȗ'), + (0x217, 'V'), + (0x218, 'M', u'ș'), + (0x219, 'V'), + (0x21A, 'M', u'ț'), + (0x21B, 'V'), + (0x21C, 'M', u'ȝ'), + (0x21D, 'V'), + (0x21E, 'M', u'ȟ'), + (0x21F, 'V'), + (0x220, 'M', u'ƞ'), + (0x221, 'V'), + (0x222, 'M', u'ȣ'), + (0x223, 'V'), + (0x224, 'M', u'ȥ'), + (0x225, 'V'), + (0x226, 'M', u'ȧ'), + (0x227, 'V'), + (0x228, 'M', u'ȩ'), + (0x229, 'V'), + (0x22A, 'M', u'ȫ'), + (0x22B, 'V'), + (0x22C, 'M', u'ȭ'), + (0x22D, 'V'), + (0x22E, 'M', u'ȯ'), + (0x22F, 'V'), + (0x230, 'M', u'ȱ'), + (0x231, 'V'), + (0x232, 'M', u'ȳ'), + (0x233, 'V'), + (0x23A, 'M', u'ⱥ'), + (0x23B, 'M', u'ȼ'), + (0x23C, 'V'), + (0x23D, 'M', u'ƚ'), + (0x23E, 'M', u'ⱦ'), + (0x23F, 'V'), + (0x241, 'M', u'ɂ'), + (0x242, 'V'), + (0x243, 'M', u'ƀ'), + (0x244, 'M', u'ʉ'), + (0x245, 'M', u'ʌ'), + (0x246, 'M', u'ɇ'), + (0x247, 'V'), + (0x248, 'M', u'ɉ'), + (0x249, 'V'), + (0x24A, 'M', u'ɋ'), + (0x24B, 'V'), + (0x24C, 'M', u'ɍ'), + (0x24D, 'V'), + (0x24E, 'M', u'ɏ'), + (0x24F, 'V'), + (0x2B0, 'M', u'h'), + (0x2B1, 'M', u'ɦ'), + (0x2B2, 'M', u'j'), + (0x2B3, 'M', u'r'), + (0x2B4, 'M', u'ɹ'), + (0x2B5, 'M', u'ɻ'), + (0x2B6, 'M', u'ʁ'), + (0x2B7, 'M', u'w'), + (0x2B8, 'M', u'y'), + (0x2B9, 'V'), + (0x2D8, '3', u' ̆'), + (0x2D9, '3', u' ̇'), + (0x2DA, '3', u' ̊'), + (0x2DB, '3', u' ̨'), + (0x2DC, '3', u' ̃'), + (0x2DD, '3', u' ̋'), + (0x2DE, 'V'), + (0x2E0, 'M', u'ɣ'), + (0x2E1, 'M', u'l'), + (0x2E2, 'M', u's'), + (0x2E3, 'M', u'x'), + (0x2E4, 'M', u'ʕ'), + (0x2E5, 'V'), + (0x340, 'M', u'̀'), + (0x341, 'M', u'́'), + (0x342, 'V'), + (0x343, 'M', u'̓'), + (0x344, 'M', u'̈́'), + (0x345, 'M', u'ι'), + (0x346, 'V'), + (0x34F, 'I'), + (0x350, 'V'), + (0x370, 'M', u'ͱ'), + (0x371, 'V'), + (0x372, 'M', u'ͳ'), + (0x373, 'V'), + (0x374, 'M', u'ʹ'), + (0x375, 'V'), + (0x376, 'M', u'ͷ'), + (0x377, 'V'), + ] + +def _seg_6(): + return [ + (0x378, 'X'), + (0x37A, '3', u' ι'), + (0x37B, 'V'), + (0x37E, '3', u';'), + (0x37F, 'M', u'ϳ'), + (0x380, 'X'), + (0x384, '3', u' ́'), + (0x385, '3', u' ̈́'), + (0x386, 'M', u'ά'), + (0x387, 'M', u'·'), + (0x388, 'M', u'έ'), + (0x389, 'M', u'ή'), + (0x38A, 'M', u'ί'), + (0x38B, 'X'), + (0x38C, 'M', u'ό'), + (0x38D, 'X'), + (0x38E, 'M', u'ύ'), + (0x38F, 'M', u'ώ'), + (0x390, 'V'), + (0x391, 'M', u'α'), + (0x392, 'M', u'β'), + (0x393, 'M', u'γ'), + (0x394, 'M', u'δ'), + (0x395, 'M', u'ε'), + (0x396, 'M', u'ζ'), + (0x397, 'M', u'η'), + (0x398, 'M', u'θ'), + (0x399, 'M', u'ι'), + (0x39A, 'M', u'κ'), + (0x39B, 'M', u'λ'), + (0x39C, 'M', u'μ'), + (0x39D, 'M', u'ν'), + (0x39E, 'M', u'ξ'), + (0x39F, 'M', u'ο'), + (0x3A0, 'M', u'π'), + (0x3A1, 'M', u'ρ'), + (0x3A2, 'X'), + (0x3A3, 'M', u'σ'), + (0x3A4, 'M', u'τ'), + (0x3A5, 'M', u'υ'), + (0x3A6, 'M', u'φ'), + (0x3A7, 'M', u'χ'), + (0x3A8, 'M', u'ψ'), + (0x3A9, 'M', u'ω'), + (0x3AA, 'M', u'ϊ'), + (0x3AB, 'M', u'ϋ'), + (0x3AC, 'V'), + (0x3C2, 'D', u'σ'), + (0x3C3, 'V'), + (0x3CF, 'M', u'ϗ'), + (0x3D0, 'M', u'β'), + (0x3D1, 'M', u'θ'), + (0x3D2, 'M', u'υ'), + (0x3D3, 'M', u'ύ'), + (0x3D4, 'M', u'ϋ'), + (0x3D5, 'M', u'φ'), + (0x3D6, 'M', u'π'), + (0x3D7, 'V'), + (0x3D8, 'M', u'ϙ'), + (0x3D9, 'V'), + (0x3DA, 'M', u'ϛ'), + (0x3DB, 'V'), + (0x3DC, 'M', u'ϝ'), + (0x3DD, 'V'), + (0x3DE, 'M', u'ϟ'), + (0x3DF, 'V'), + (0x3E0, 'M', u'ϡ'), + (0x3E1, 'V'), + (0x3E2, 'M', u'ϣ'), + (0x3E3, 'V'), + (0x3E4, 'M', u'ϥ'), + (0x3E5, 'V'), + (0x3E6, 'M', u'ϧ'), + (0x3E7, 'V'), + (0x3E8, 'M', u'ϩ'), + (0x3E9, 'V'), + (0x3EA, 'M', u'ϫ'), + (0x3EB, 'V'), + (0x3EC, 'M', u'ϭ'), + (0x3ED, 'V'), + (0x3EE, 'M', u'ϯ'), + (0x3EF, 'V'), + (0x3F0, 'M', u'κ'), + (0x3F1, 'M', u'ρ'), + (0x3F2, 'M', u'σ'), + (0x3F3, 'V'), + (0x3F4, 'M', u'θ'), + (0x3F5, 'M', u'ε'), + (0x3F6, 'V'), + (0x3F7, 'M', u'ϸ'), + (0x3F8, 'V'), + (0x3F9, 'M', u'σ'), + (0x3FA, 'M', u'ϻ'), + (0x3FB, 'V'), + (0x3FD, 'M', u'ͻ'), + (0x3FE, 'M', u'ͼ'), + (0x3FF, 'M', u'ͽ'), + (0x400, 'M', u'ѐ'), + (0x401, 'M', u'ё'), + (0x402, 'M', u'ђ'), + ] + +def _seg_7(): + return [ + (0x403, 'M', u'ѓ'), + (0x404, 'M', u'є'), + (0x405, 'M', u'ѕ'), + (0x406, 'M', u'і'), + (0x407, 'M', u'ї'), + (0x408, 'M', u'ј'), + (0x409, 'M', u'љ'), + (0x40A, 'M', u'њ'), + (0x40B, 'M', u'ћ'), + (0x40C, 'M', u'ќ'), + (0x40D, 'M', u'ѝ'), + (0x40E, 'M', u'ў'), + (0x40F, 'M', u'џ'), + (0x410, 'M', u'а'), + (0x411, 'M', u'б'), + (0x412, 'M', u'в'), + (0x413, 'M', u'г'), + (0x414, 'M', u'д'), + (0x415, 'M', u'е'), + (0x416, 'M', u'ж'), + (0x417, 'M', u'з'), + (0x418, 'M', u'и'), + (0x419, 'M', u'й'), + (0x41A, 'M', u'к'), + (0x41B, 'M', u'л'), + (0x41C, 'M', u'м'), + (0x41D, 'M', u'н'), + (0x41E, 'M', u'о'), + (0x41F, 'M', u'п'), + (0x420, 'M', u'р'), + (0x421, 'M', u'с'), + (0x422, 'M', u'т'), + (0x423, 'M', u'у'), + (0x424, 'M', u'ф'), + (0x425, 'M', u'х'), + (0x426, 'M', u'ц'), + (0x427, 'M', u'ч'), + (0x428, 'M', u'ш'), + (0x429, 'M', u'щ'), + (0x42A, 'M', u'ъ'), + (0x42B, 'M', u'ы'), + (0x42C, 'M', u'ь'), + (0x42D, 'M', u'э'), + (0x42E, 'M', u'ю'), + (0x42F, 'M', u'я'), + (0x430, 'V'), + (0x460, 'M', u'ѡ'), + (0x461, 'V'), + (0x462, 'M', u'ѣ'), + (0x463, 'V'), + (0x464, 'M', u'ѥ'), + (0x465, 'V'), + (0x466, 'M', u'ѧ'), + (0x467, 'V'), + (0x468, 'M', u'ѩ'), + (0x469, 'V'), + (0x46A, 'M', u'ѫ'), + (0x46B, 'V'), + (0x46C, 'M', u'ѭ'), + (0x46D, 'V'), + (0x46E, 'M', u'ѯ'), + (0x46F, 'V'), + (0x470, 'M', u'ѱ'), + (0x471, 'V'), + (0x472, 'M', u'ѳ'), + (0x473, 'V'), + (0x474, 'M', u'ѵ'), + (0x475, 'V'), + (0x476, 'M', u'ѷ'), + (0x477, 'V'), + (0x478, 'M', u'ѹ'), + (0x479, 'V'), + (0x47A, 'M', u'ѻ'), + (0x47B, 'V'), + (0x47C, 'M', u'ѽ'), + (0x47D, 'V'), + (0x47E, 'M', u'ѿ'), + (0x47F, 'V'), + (0x480, 'M', u'ҁ'), + (0x481, 'V'), + (0x48A, 'M', u'ҋ'), + (0x48B, 'V'), + (0x48C, 'M', u'ҍ'), + (0x48D, 'V'), + (0x48E, 'M', u'ҏ'), + (0x48F, 'V'), + (0x490, 'M', u'ґ'), + (0x491, 'V'), + (0x492, 'M', u'ғ'), + (0x493, 'V'), + (0x494, 'M', u'ҕ'), + (0x495, 'V'), + (0x496, 'M', u'җ'), + (0x497, 'V'), + (0x498, 'M', u'ҙ'), + (0x499, 'V'), + (0x49A, 'M', u'қ'), + (0x49B, 'V'), + (0x49C, 'M', u'ҝ'), + (0x49D, 'V'), + ] + +def _seg_8(): + return [ + (0x49E, 'M', u'ҟ'), + (0x49F, 'V'), + (0x4A0, 'M', u'ҡ'), + (0x4A1, 'V'), + (0x4A2, 'M', u'ң'), + (0x4A3, 'V'), + (0x4A4, 'M', u'ҥ'), + (0x4A5, 'V'), + (0x4A6, 'M', u'ҧ'), + (0x4A7, 'V'), + (0x4A8, 'M', u'ҩ'), + (0x4A9, 'V'), + (0x4AA, 'M', u'ҫ'), + (0x4AB, 'V'), + (0x4AC, 'M', u'ҭ'), + (0x4AD, 'V'), + (0x4AE, 'M', u'ү'), + (0x4AF, 'V'), + (0x4B0, 'M', u'ұ'), + (0x4B1, 'V'), + (0x4B2, 'M', u'ҳ'), + (0x4B3, 'V'), + (0x4B4, 'M', u'ҵ'), + (0x4B5, 'V'), + (0x4B6, 'M', u'ҷ'), + (0x4B7, 'V'), + (0x4B8, 'M', u'ҹ'), + (0x4B9, 'V'), + (0x4BA, 'M', u'һ'), + (0x4BB, 'V'), + (0x4BC, 'M', u'ҽ'), + (0x4BD, 'V'), + (0x4BE, 'M', u'ҿ'), + (0x4BF, 'V'), + (0x4C0, 'X'), + (0x4C1, 'M', u'ӂ'), + (0x4C2, 'V'), + (0x4C3, 'M', u'ӄ'), + (0x4C4, 'V'), + (0x4C5, 'M', u'ӆ'), + (0x4C6, 'V'), + (0x4C7, 'M', u'ӈ'), + (0x4C8, 'V'), + (0x4C9, 'M', u'ӊ'), + (0x4CA, 'V'), + (0x4CB, 'M', u'ӌ'), + (0x4CC, 'V'), + (0x4CD, 'M', u'ӎ'), + (0x4CE, 'V'), + (0x4D0, 'M', u'ӑ'), + (0x4D1, 'V'), + (0x4D2, 'M', u'ӓ'), + (0x4D3, 'V'), + (0x4D4, 'M', u'ӕ'), + (0x4D5, 'V'), + (0x4D6, 'M', u'ӗ'), + (0x4D7, 'V'), + (0x4D8, 'M', u'ә'), + (0x4D9, 'V'), + (0x4DA, 'M', u'ӛ'), + (0x4DB, 'V'), + (0x4DC, 'M', u'ӝ'), + (0x4DD, 'V'), + (0x4DE, 'M', u'ӟ'), + (0x4DF, 'V'), + (0x4E0, 'M', u'ӡ'), + (0x4E1, 'V'), + (0x4E2, 'M', u'ӣ'), + (0x4E3, 'V'), + (0x4E4, 'M', u'ӥ'), + (0x4E5, 'V'), + (0x4E6, 'M', u'ӧ'), + (0x4E7, 'V'), + (0x4E8, 'M', u'ө'), + (0x4E9, 'V'), + (0x4EA, 'M', u'ӫ'), + (0x4EB, 'V'), + (0x4EC, 'M', u'ӭ'), + (0x4ED, 'V'), + (0x4EE, 'M', u'ӯ'), + (0x4EF, 'V'), + (0x4F0, 'M', u'ӱ'), + (0x4F1, 'V'), + (0x4F2, 'M', u'ӳ'), + (0x4F3, 'V'), + (0x4F4, 'M', u'ӵ'), + (0x4F5, 'V'), + (0x4F6, 'M', u'ӷ'), + (0x4F7, 'V'), + (0x4F8, 'M', u'ӹ'), + (0x4F9, 'V'), + (0x4FA, 'M', u'ӻ'), + (0x4FB, 'V'), + (0x4FC, 'M', u'ӽ'), + (0x4FD, 'V'), + (0x4FE, 'M', u'ӿ'), + (0x4FF, 'V'), + (0x500, 'M', u'ԁ'), + (0x501, 'V'), + (0x502, 'M', u'ԃ'), + ] + +def _seg_9(): + return [ + (0x503, 'V'), + (0x504, 'M', u'ԅ'), + (0x505, 'V'), + (0x506, 'M', u'ԇ'), + (0x507, 'V'), + (0x508, 'M', u'ԉ'), + (0x509, 'V'), + (0x50A, 'M', u'ԋ'), + (0x50B, 'V'), + (0x50C, 'M', u'ԍ'), + (0x50D, 'V'), + (0x50E, 'M', u'ԏ'), + (0x50F, 'V'), + (0x510, 'M', u'ԑ'), + (0x511, 'V'), + (0x512, 'M', u'ԓ'), + (0x513, 'V'), + (0x514, 'M', u'ԕ'), + (0x515, 'V'), + (0x516, 'M', u'ԗ'), + (0x517, 'V'), + (0x518, 'M', u'ԙ'), + (0x519, 'V'), + (0x51A, 'M', u'ԛ'), + (0x51B, 'V'), + (0x51C, 'M', u'ԝ'), + (0x51D, 'V'), + (0x51E, 'M', u'ԟ'), + (0x51F, 'V'), + (0x520, 'M', u'ԡ'), + (0x521, 'V'), + (0x522, 'M', u'ԣ'), + (0x523, 'V'), + (0x524, 'M', u'ԥ'), + (0x525, 'V'), + (0x526, 'M', u'ԧ'), + (0x527, 'V'), + (0x528, 'M', u'ԩ'), + (0x529, 'V'), + (0x52A, 'M', u'ԫ'), + (0x52B, 'V'), + (0x52C, 'M', u'ԭ'), + (0x52D, 'V'), + (0x52E, 'M', u'ԯ'), + (0x52F, 'V'), + (0x530, 'X'), + (0x531, 'M', u'ա'), + (0x532, 'M', u'բ'), + (0x533, 'M', u'գ'), + (0x534, 'M', u'դ'), + (0x535, 'M', u'ե'), + (0x536, 'M', u'զ'), + (0x537, 'M', u'է'), + (0x538, 'M', u'ը'), + (0x539, 'M', u'թ'), + (0x53A, 'M', u'ժ'), + (0x53B, 'M', u'ի'), + (0x53C, 'M', u'լ'), + (0x53D, 'M', u'խ'), + (0x53E, 'M', u'ծ'), + (0x53F, 'M', u'կ'), + (0x540, 'M', u'հ'), + (0x541, 'M', u'ձ'), + (0x542, 'M', u'ղ'), + (0x543, 'M', u'ճ'), + (0x544, 'M', u'մ'), + (0x545, 'M', u'յ'), + (0x546, 'M', u'ն'), + (0x547, 'M', u'շ'), + (0x548, 'M', u'ո'), + (0x549, 'M', u'չ'), + (0x54A, 'M', u'պ'), + (0x54B, 'M', u'ջ'), + (0x54C, 'M', u'ռ'), + (0x54D, 'M', u'ս'), + (0x54E, 'M', u'վ'), + (0x54F, 'M', u'տ'), + (0x550, 'M', u'ր'), + (0x551, 'M', u'ց'), + (0x552, 'M', u'ւ'), + (0x553, 'M', u'փ'), + (0x554, 'M', u'ք'), + (0x555, 'M', u'օ'), + (0x556, 'M', u'ֆ'), + (0x557, 'X'), + (0x559, 'V'), + (0x587, 'M', u'եւ'), + (0x588, 'V'), + (0x58B, 'X'), + (0x58D, 'V'), + (0x590, 'X'), + (0x591, 'V'), + (0x5C8, 'X'), + (0x5D0, 'V'), + (0x5EB, 'X'), + (0x5EF, 'V'), + (0x5F5, 'X'), + (0x606, 'V'), + (0x61C, 'X'), + (0x61E, 'V'), + ] + +def _seg_10(): + return [ + (0x675, 'M', u'اٴ'), + (0x676, 'M', u'وٴ'), + (0x677, 'M', u'ۇٴ'), + (0x678, 'M', u'يٴ'), + (0x679, 'V'), + (0x6DD, 'X'), + (0x6DE, 'V'), + (0x70E, 'X'), + (0x710, 'V'), + (0x74B, 'X'), + (0x74D, 'V'), + (0x7B2, 'X'), + (0x7C0, 'V'), + (0x7FB, 'X'), + (0x7FD, 'V'), + (0x82E, 'X'), + (0x830, 'V'), + (0x83F, 'X'), + (0x840, 'V'), + (0x85C, 'X'), + (0x85E, 'V'), + (0x85F, 'X'), + (0x860, 'V'), + (0x86B, 'X'), + (0x8A0, 'V'), + (0x8B5, 'X'), + (0x8B6, 'V'), + (0x8BE, 'X'), + (0x8D3, 'V'), + (0x8E2, 'X'), + (0x8E3, 'V'), + (0x958, 'M', u'क़'), + (0x959, 'M', u'ख़'), + (0x95A, 'M', u'ग़'), + (0x95B, 'M', u'ज़'), + (0x95C, 'M', u'ड़'), + (0x95D, 'M', u'ढ़'), + (0x95E, 'M', u'फ़'), + (0x95F, 'M', u'य़'), + (0x960, 'V'), + (0x984, 'X'), + (0x985, 'V'), + (0x98D, 'X'), + (0x98F, 'V'), + (0x991, 'X'), + (0x993, 'V'), + (0x9A9, 'X'), + (0x9AA, 'V'), + (0x9B1, 'X'), + (0x9B2, 'V'), + (0x9B3, 'X'), + (0x9B6, 'V'), + (0x9BA, 'X'), + (0x9BC, 'V'), + (0x9C5, 'X'), + (0x9C7, 'V'), + (0x9C9, 'X'), + (0x9CB, 'V'), + (0x9CF, 'X'), + (0x9D7, 'V'), + (0x9D8, 'X'), + (0x9DC, 'M', u'ড়'), + (0x9DD, 'M', u'ঢ়'), + (0x9DE, 'X'), + (0x9DF, 'M', u'য়'), + (0x9E0, 'V'), + (0x9E4, 'X'), + (0x9E6, 'V'), + (0x9FF, 'X'), + (0xA01, 'V'), + (0xA04, 'X'), + (0xA05, 'V'), + (0xA0B, 'X'), + (0xA0F, 'V'), + (0xA11, 'X'), + (0xA13, 'V'), + (0xA29, 'X'), + (0xA2A, 'V'), + (0xA31, 'X'), + (0xA32, 'V'), + (0xA33, 'M', u'ਲ਼'), + (0xA34, 'X'), + (0xA35, 'V'), + (0xA36, 'M', u'ਸ਼'), + (0xA37, 'X'), + (0xA38, 'V'), + (0xA3A, 'X'), + (0xA3C, 'V'), + (0xA3D, 'X'), + (0xA3E, 'V'), + (0xA43, 'X'), + (0xA47, 'V'), + (0xA49, 'X'), + (0xA4B, 'V'), + (0xA4E, 'X'), + (0xA51, 'V'), + (0xA52, 'X'), + (0xA59, 'M', u'ਖ਼'), + (0xA5A, 'M', u'ਗ਼'), + (0xA5B, 'M', u'ਜ਼'), + ] + +def _seg_11(): + return [ + (0xA5C, 'V'), + (0xA5D, 'X'), + (0xA5E, 'M', u'ਫ਼'), + (0xA5F, 'X'), + (0xA66, 'V'), + (0xA77, 'X'), + (0xA81, 'V'), + (0xA84, 'X'), + (0xA85, 'V'), + (0xA8E, 'X'), + (0xA8F, 'V'), + (0xA92, 'X'), + (0xA93, 'V'), + (0xAA9, 'X'), + (0xAAA, 'V'), + (0xAB1, 'X'), + (0xAB2, 'V'), + (0xAB4, 'X'), + (0xAB5, 'V'), + (0xABA, 'X'), + (0xABC, 'V'), + (0xAC6, 'X'), + (0xAC7, 'V'), + (0xACA, 'X'), + (0xACB, 'V'), + (0xACE, 'X'), + (0xAD0, 'V'), + (0xAD1, 'X'), + (0xAE0, 'V'), + (0xAE4, 'X'), + (0xAE6, 'V'), + (0xAF2, 'X'), + (0xAF9, 'V'), + (0xB00, 'X'), + (0xB01, 'V'), + (0xB04, 'X'), + (0xB05, 'V'), + (0xB0D, 'X'), + (0xB0F, 'V'), + (0xB11, 'X'), + (0xB13, 'V'), + (0xB29, 'X'), + (0xB2A, 'V'), + (0xB31, 'X'), + (0xB32, 'V'), + (0xB34, 'X'), + (0xB35, 'V'), + (0xB3A, 'X'), + (0xB3C, 'V'), + (0xB45, 'X'), + (0xB47, 'V'), + (0xB49, 'X'), + (0xB4B, 'V'), + (0xB4E, 'X'), + (0xB56, 'V'), + (0xB58, 'X'), + (0xB5C, 'M', u'ଡ଼'), + (0xB5D, 'M', u'ଢ଼'), + (0xB5E, 'X'), + (0xB5F, 'V'), + (0xB64, 'X'), + (0xB66, 'V'), + (0xB78, 'X'), + (0xB82, 'V'), + (0xB84, 'X'), + (0xB85, 'V'), + (0xB8B, 'X'), + (0xB8E, 'V'), + (0xB91, 'X'), + (0xB92, 'V'), + (0xB96, 'X'), + (0xB99, 'V'), + (0xB9B, 'X'), + (0xB9C, 'V'), + (0xB9D, 'X'), + (0xB9E, 'V'), + (0xBA0, 'X'), + (0xBA3, 'V'), + (0xBA5, 'X'), + (0xBA8, 'V'), + (0xBAB, 'X'), + (0xBAE, 'V'), + (0xBBA, 'X'), + (0xBBE, 'V'), + (0xBC3, 'X'), + (0xBC6, 'V'), + (0xBC9, 'X'), + (0xBCA, 'V'), + (0xBCE, 'X'), + (0xBD0, 'V'), + (0xBD1, 'X'), + (0xBD7, 'V'), + (0xBD8, 'X'), + (0xBE6, 'V'), + (0xBFB, 'X'), + (0xC00, 'V'), + (0xC0D, 'X'), + (0xC0E, 'V'), + (0xC11, 'X'), + (0xC12, 'V'), + ] + +def _seg_12(): + return [ + (0xC29, 'X'), + (0xC2A, 'V'), + (0xC3A, 'X'), + (0xC3D, 'V'), + (0xC45, 'X'), + (0xC46, 'V'), + (0xC49, 'X'), + (0xC4A, 'V'), + (0xC4E, 'X'), + (0xC55, 'V'), + (0xC57, 'X'), + (0xC58, 'V'), + (0xC5B, 'X'), + (0xC60, 'V'), + (0xC64, 'X'), + (0xC66, 'V'), + (0xC70, 'X'), + (0xC78, 'V'), + (0xC8D, 'X'), + (0xC8E, 'V'), + (0xC91, 'X'), + (0xC92, 'V'), + (0xCA9, 'X'), + (0xCAA, 'V'), + (0xCB4, 'X'), + (0xCB5, 'V'), + (0xCBA, 'X'), + (0xCBC, 'V'), + (0xCC5, 'X'), + (0xCC6, 'V'), + (0xCC9, 'X'), + (0xCCA, 'V'), + (0xCCE, 'X'), + (0xCD5, 'V'), + (0xCD7, 'X'), + (0xCDE, 'V'), + (0xCDF, 'X'), + (0xCE0, 'V'), + (0xCE4, 'X'), + (0xCE6, 'V'), + (0xCF0, 'X'), + (0xCF1, 'V'), + (0xCF3, 'X'), + (0xD00, 'V'), + (0xD04, 'X'), + (0xD05, 'V'), + (0xD0D, 'X'), + (0xD0E, 'V'), + (0xD11, 'X'), + (0xD12, 'V'), + (0xD45, 'X'), + (0xD46, 'V'), + (0xD49, 'X'), + (0xD4A, 'V'), + (0xD50, 'X'), + (0xD54, 'V'), + (0xD64, 'X'), + (0xD66, 'V'), + (0xD80, 'X'), + (0xD82, 'V'), + (0xD84, 'X'), + (0xD85, 'V'), + (0xD97, 'X'), + (0xD9A, 'V'), + (0xDB2, 'X'), + (0xDB3, 'V'), + (0xDBC, 'X'), + (0xDBD, 'V'), + (0xDBE, 'X'), + (0xDC0, 'V'), + (0xDC7, 'X'), + (0xDCA, 'V'), + (0xDCB, 'X'), + (0xDCF, 'V'), + (0xDD5, 'X'), + (0xDD6, 'V'), + (0xDD7, 'X'), + (0xDD8, 'V'), + (0xDE0, 'X'), + (0xDE6, 'V'), + (0xDF0, 'X'), + (0xDF2, 'V'), + (0xDF5, 'X'), + (0xE01, 'V'), + (0xE33, 'M', u'ํา'), + (0xE34, 'V'), + (0xE3B, 'X'), + (0xE3F, 'V'), + (0xE5C, 'X'), + (0xE81, 'V'), + (0xE83, 'X'), + (0xE84, 'V'), + (0xE85, 'X'), + (0xE87, 'V'), + (0xE89, 'X'), + (0xE8A, 'V'), + (0xE8B, 'X'), + (0xE8D, 'V'), + (0xE8E, 'X'), + (0xE94, 'V'), + ] + +def _seg_13(): + return [ + (0xE98, 'X'), + (0xE99, 'V'), + (0xEA0, 'X'), + (0xEA1, 'V'), + (0xEA4, 'X'), + (0xEA5, 'V'), + (0xEA6, 'X'), + (0xEA7, 'V'), + (0xEA8, 'X'), + (0xEAA, 'V'), + (0xEAC, 'X'), + (0xEAD, 'V'), + (0xEB3, 'M', u'ໍາ'), + (0xEB4, 'V'), + (0xEBA, 'X'), + (0xEBB, 'V'), + (0xEBE, 'X'), + (0xEC0, 'V'), + (0xEC5, 'X'), + (0xEC6, 'V'), + (0xEC7, 'X'), + (0xEC8, 'V'), + (0xECE, 'X'), + (0xED0, 'V'), + (0xEDA, 'X'), + (0xEDC, 'M', u'ຫນ'), + (0xEDD, 'M', u'ຫມ'), + (0xEDE, 'V'), + (0xEE0, 'X'), + (0xF00, 'V'), + (0xF0C, 'M', u'་'), + (0xF0D, 'V'), + (0xF43, 'M', u'གྷ'), + (0xF44, 'V'), + (0xF48, 'X'), + (0xF49, 'V'), + (0xF4D, 'M', u'ཌྷ'), + (0xF4E, 'V'), + (0xF52, 'M', u'དྷ'), + (0xF53, 'V'), + (0xF57, 'M', u'བྷ'), + (0xF58, 'V'), + (0xF5C, 'M', u'ཛྷ'), + (0xF5D, 'V'), + (0xF69, 'M', u'ཀྵ'), + (0xF6A, 'V'), + (0xF6D, 'X'), + (0xF71, 'V'), + (0xF73, 'M', u'ཱི'), + (0xF74, 'V'), + (0xF75, 'M', u'ཱུ'), + (0xF76, 'M', u'ྲྀ'), + (0xF77, 'M', u'ྲཱྀ'), + (0xF78, 'M', u'ླྀ'), + (0xF79, 'M', u'ླཱྀ'), + (0xF7A, 'V'), + (0xF81, 'M', u'ཱྀ'), + (0xF82, 'V'), + (0xF93, 'M', u'ྒྷ'), + (0xF94, 'V'), + (0xF98, 'X'), + (0xF99, 'V'), + (0xF9D, 'M', u'ྜྷ'), + (0xF9E, 'V'), + (0xFA2, 'M', u'ྡྷ'), + (0xFA3, 'V'), + (0xFA7, 'M', u'ྦྷ'), + (0xFA8, 'V'), + (0xFAC, 'M', u'ྫྷ'), + (0xFAD, 'V'), + (0xFB9, 'M', u'ྐྵ'), + (0xFBA, 'V'), + (0xFBD, 'X'), + (0xFBE, 'V'), + (0xFCD, 'X'), + (0xFCE, 'V'), + (0xFDB, 'X'), + (0x1000, 'V'), + (0x10A0, 'X'), + (0x10C7, 'M', u'ⴧ'), + (0x10C8, 'X'), + (0x10CD, 'M', u'ⴭ'), + (0x10CE, 'X'), + (0x10D0, 'V'), + (0x10FC, 'M', u'ნ'), + (0x10FD, 'V'), + (0x115F, 'X'), + (0x1161, 'V'), + (0x1249, 'X'), + (0x124A, 'V'), + (0x124E, 'X'), + (0x1250, 'V'), + (0x1257, 'X'), + (0x1258, 'V'), + (0x1259, 'X'), + (0x125A, 'V'), + (0x125E, 'X'), + (0x1260, 'V'), + (0x1289, 'X'), + (0x128A, 'V'), + ] + +def _seg_14(): + return [ + (0x128E, 'X'), + (0x1290, 'V'), + (0x12B1, 'X'), + (0x12B2, 'V'), + (0x12B6, 'X'), + (0x12B8, 'V'), + (0x12BF, 'X'), + (0x12C0, 'V'), + (0x12C1, 'X'), + (0x12C2, 'V'), + (0x12C6, 'X'), + (0x12C8, 'V'), + (0x12D7, 'X'), + (0x12D8, 'V'), + (0x1311, 'X'), + (0x1312, 'V'), + (0x1316, 'X'), + (0x1318, 'V'), + (0x135B, 'X'), + (0x135D, 'V'), + (0x137D, 'X'), + (0x1380, 'V'), + (0x139A, 'X'), + (0x13A0, 'V'), + (0x13F6, 'X'), + (0x13F8, 'M', u'Ᏸ'), + (0x13F9, 'M', u'Ᏹ'), + (0x13FA, 'M', u'Ᏺ'), + (0x13FB, 'M', u'Ᏻ'), + (0x13FC, 'M', u'Ᏼ'), + (0x13FD, 'M', u'Ᏽ'), + (0x13FE, 'X'), + (0x1400, 'V'), + (0x1680, 'X'), + (0x1681, 'V'), + (0x169D, 'X'), + (0x16A0, 'V'), + (0x16F9, 'X'), + (0x1700, 'V'), + (0x170D, 'X'), + (0x170E, 'V'), + (0x1715, 'X'), + (0x1720, 'V'), + (0x1737, 'X'), + (0x1740, 'V'), + (0x1754, 'X'), + (0x1760, 'V'), + (0x176D, 'X'), + (0x176E, 'V'), + (0x1771, 'X'), + (0x1772, 'V'), + (0x1774, 'X'), + (0x1780, 'V'), + (0x17B4, 'X'), + (0x17B6, 'V'), + (0x17DE, 'X'), + (0x17E0, 'V'), + (0x17EA, 'X'), + (0x17F0, 'V'), + (0x17FA, 'X'), + (0x1800, 'V'), + (0x1806, 'X'), + (0x1807, 'V'), + (0x180B, 'I'), + (0x180E, 'X'), + (0x1810, 'V'), + (0x181A, 'X'), + (0x1820, 'V'), + (0x1879, 'X'), + (0x1880, 'V'), + (0x18AB, 'X'), + (0x18B0, 'V'), + (0x18F6, 'X'), + (0x1900, 'V'), + (0x191F, 'X'), + (0x1920, 'V'), + (0x192C, 'X'), + (0x1930, 'V'), + (0x193C, 'X'), + (0x1940, 'V'), + (0x1941, 'X'), + (0x1944, 'V'), + (0x196E, 'X'), + (0x1970, 'V'), + (0x1975, 'X'), + (0x1980, 'V'), + (0x19AC, 'X'), + (0x19B0, 'V'), + (0x19CA, 'X'), + (0x19D0, 'V'), + (0x19DB, 'X'), + (0x19DE, 'V'), + (0x1A1C, 'X'), + (0x1A1E, 'V'), + (0x1A5F, 'X'), + (0x1A60, 'V'), + (0x1A7D, 'X'), + (0x1A7F, 'V'), + (0x1A8A, 'X'), + (0x1A90, 'V'), + ] + +def _seg_15(): + return [ + (0x1A9A, 'X'), + (0x1AA0, 'V'), + (0x1AAE, 'X'), + (0x1AB0, 'V'), + (0x1ABF, 'X'), + (0x1B00, 'V'), + (0x1B4C, 'X'), + (0x1B50, 'V'), + (0x1B7D, 'X'), + (0x1B80, 'V'), + (0x1BF4, 'X'), + (0x1BFC, 'V'), + (0x1C38, 'X'), + (0x1C3B, 'V'), + (0x1C4A, 'X'), + (0x1C4D, 'V'), + (0x1C80, 'M', u'в'), + (0x1C81, 'M', u'д'), + (0x1C82, 'M', u'о'), + (0x1C83, 'M', u'с'), + (0x1C84, 'M', u'т'), + (0x1C86, 'M', u'ъ'), + (0x1C87, 'M', u'ѣ'), + (0x1C88, 'M', u'ꙋ'), + (0x1C89, 'X'), + (0x1CC0, 'V'), + (0x1CC8, 'X'), + (0x1CD0, 'V'), + (0x1CFA, 'X'), + (0x1D00, 'V'), + (0x1D2C, 'M', u'a'), + (0x1D2D, 'M', u'æ'), + (0x1D2E, 'M', u'b'), + (0x1D2F, 'V'), + (0x1D30, 'M', u'd'), + (0x1D31, 'M', u'e'), + (0x1D32, 'M', u'ǝ'), + (0x1D33, 'M', u'g'), + (0x1D34, 'M', u'h'), + (0x1D35, 'M', u'i'), + (0x1D36, 'M', u'j'), + (0x1D37, 'M', u'k'), + (0x1D38, 'M', u'l'), + (0x1D39, 'M', u'm'), + (0x1D3A, 'M', u'n'), + (0x1D3B, 'V'), + (0x1D3C, 'M', u'o'), + (0x1D3D, 'M', u'ȣ'), + (0x1D3E, 'M', u'p'), + (0x1D3F, 'M', u'r'), + (0x1D40, 'M', u't'), + (0x1D41, 'M', u'u'), + (0x1D42, 'M', u'w'), + (0x1D43, 'M', u'a'), + (0x1D44, 'M', u'ɐ'), + (0x1D45, 'M', u'ɑ'), + (0x1D46, 'M', u'ᴂ'), + (0x1D47, 'M', u'b'), + (0x1D48, 'M', u'd'), + (0x1D49, 'M', u'e'), + (0x1D4A, 'M', u'ə'), + (0x1D4B, 'M', u'ɛ'), + (0x1D4C, 'M', u'ɜ'), + (0x1D4D, 'M', u'g'), + (0x1D4E, 'V'), + (0x1D4F, 'M', u'k'), + (0x1D50, 'M', u'm'), + (0x1D51, 'M', u'ŋ'), + (0x1D52, 'M', u'o'), + (0x1D53, 'M', u'ɔ'), + (0x1D54, 'M', u'ᴖ'), + (0x1D55, 'M', u'ᴗ'), + (0x1D56, 'M', u'p'), + (0x1D57, 'M', u't'), + (0x1D58, 'M', u'u'), + (0x1D59, 'M', u'ᴝ'), + (0x1D5A, 'M', u'ɯ'), + (0x1D5B, 'M', u'v'), + (0x1D5C, 'M', u'ᴥ'), + (0x1D5D, 'M', u'β'), + (0x1D5E, 'M', u'γ'), + (0x1D5F, 'M', u'δ'), + (0x1D60, 'M', u'φ'), + (0x1D61, 'M', u'χ'), + (0x1D62, 'M', u'i'), + (0x1D63, 'M', u'r'), + (0x1D64, 'M', u'u'), + (0x1D65, 'M', u'v'), + (0x1D66, 'M', u'β'), + (0x1D67, 'M', u'γ'), + (0x1D68, 'M', u'ρ'), + (0x1D69, 'M', u'φ'), + (0x1D6A, 'M', u'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', u'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', u'ɒ'), + (0x1D9C, 'M', u'c'), + (0x1D9D, 'M', u'ɕ'), + (0x1D9E, 'M', u'ð'), + ] + +def _seg_16(): + return [ + (0x1D9F, 'M', u'ɜ'), + (0x1DA0, 'M', u'f'), + (0x1DA1, 'M', u'ɟ'), + (0x1DA2, 'M', u'ɡ'), + (0x1DA3, 'M', u'ɥ'), + (0x1DA4, 'M', u'ɨ'), + (0x1DA5, 'M', u'ɩ'), + (0x1DA6, 'M', u'ɪ'), + (0x1DA7, 'M', u'ᵻ'), + (0x1DA8, 'M', u'ʝ'), + (0x1DA9, 'M', u'ɭ'), + (0x1DAA, 'M', u'ᶅ'), + (0x1DAB, 'M', u'ʟ'), + (0x1DAC, 'M', u'ɱ'), + (0x1DAD, 'M', u'ɰ'), + (0x1DAE, 'M', u'ɲ'), + (0x1DAF, 'M', u'ɳ'), + (0x1DB0, 'M', u'ɴ'), + (0x1DB1, 'M', u'ɵ'), + (0x1DB2, 'M', u'ɸ'), + (0x1DB3, 'M', u'ʂ'), + (0x1DB4, 'M', u'ʃ'), + (0x1DB5, 'M', u'ƫ'), + (0x1DB6, 'M', u'ʉ'), + (0x1DB7, 'M', u'ʊ'), + (0x1DB8, 'M', u'ᴜ'), + (0x1DB9, 'M', u'ʋ'), + (0x1DBA, 'M', u'ʌ'), + (0x1DBB, 'M', u'z'), + (0x1DBC, 'M', u'ʐ'), + (0x1DBD, 'M', u'ʑ'), + (0x1DBE, 'M', u'ʒ'), + (0x1DBF, 'M', u'θ'), + (0x1DC0, 'V'), + (0x1DFA, 'X'), + (0x1DFB, 'V'), + (0x1E00, 'M', u'ḁ'), + (0x1E01, 'V'), + (0x1E02, 'M', u'ḃ'), + (0x1E03, 'V'), + (0x1E04, 'M', u'ḅ'), + (0x1E05, 'V'), + (0x1E06, 'M', u'ḇ'), + (0x1E07, 'V'), + (0x1E08, 'M', u'ḉ'), + (0x1E09, 'V'), + (0x1E0A, 'M', u'ḋ'), + (0x1E0B, 'V'), + (0x1E0C, 'M', u'ḍ'), + (0x1E0D, 'V'), + (0x1E0E, 'M', u'ḏ'), + (0x1E0F, 'V'), + (0x1E10, 'M', u'ḑ'), + (0x1E11, 'V'), + (0x1E12, 'M', u'ḓ'), + (0x1E13, 'V'), + (0x1E14, 'M', u'ḕ'), + (0x1E15, 'V'), + (0x1E16, 'M', u'ḗ'), + (0x1E17, 'V'), + (0x1E18, 'M', u'ḙ'), + (0x1E19, 'V'), + (0x1E1A, 'M', u'ḛ'), + (0x1E1B, 'V'), + (0x1E1C, 'M', u'ḝ'), + (0x1E1D, 'V'), + (0x1E1E, 'M', u'ḟ'), + (0x1E1F, 'V'), + (0x1E20, 'M', u'ḡ'), + (0x1E21, 'V'), + (0x1E22, 'M', u'ḣ'), + (0x1E23, 'V'), + (0x1E24, 'M', u'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', u'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', u'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', u'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', u'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', u'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', u'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', u'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', u'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', u'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', u'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', u'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', u'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', u'ḿ'), + (0x1E3F, 'V'), + ] + +def _seg_17(): + return [ + (0x1E40, 'M', u'ṁ'), + (0x1E41, 'V'), + (0x1E42, 'M', u'ṃ'), + (0x1E43, 'V'), + (0x1E44, 'M', u'ṅ'), + (0x1E45, 'V'), + (0x1E46, 'M', u'ṇ'), + (0x1E47, 'V'), + (0x1E48, 'M', u'ṉ'), + (0x1E49, 'V'), + (0x1E4A, 'M', u'ṋ'), + (0x1E4B, 'V'), + (0x1E4C, 'M', u'ṍ'), + (0x1E4D, 'V'), + (0x1E4E, 'M', u'ṏ'), + (0x1E4F, 'V'), + (0x1E50, 'M', u'ṑ'), + (0x1E51, 'V'), + (0x1E52, 'M', u'ṓ'), + (0x1E53, 'V'), + (0x1E54, 'M', u'ṕ'), + (0x1E55, 'V'), + (0x1E56, 'M', u'ṗ'), + (0x1E57, 'V'), + (0x1E58, 'M', u'ṙ'), + (0x1E59, 'V'), + (0x1E5A, 'M', u'ṛ'), + (0x1E5B, 'V'), + (0x1E5C, 'M', u'ṝ'), + (0x1E5D, 'V'), + (0x1E5E, 'M', u'ṟ'), + (0x1E5F, 'V'), + (0x1E60, 'M', u'ṡ'), + (0x1E61, 'V'), + (0x1E62, 'M', u'ṣ'), + (0x1E63, 'V'), + (0x1E64, 'M', u'ṥ'), + (0x1E65, 'V'), + (0x1E66, 'M', u'ṧ'), + (0x1E67, 'V'), + (0x1E68, 'M', u'ṩ'), + (0x1E69, 'V'), + (0x1E6A, 'M', u'ṫ'), + (0x1E6B, 'V'), + (0x1E6C, 'M', u'ṭ'), + (0x1E6D, 'V'), + (0x1E6E, 'M', u'ṯ'), + (0x1E6F, 'V'), + (0x1E70, 'M', u'ṱ'), + (0x1E71, 'V'), + (0x1E72, 'M', u'ṳ'), + (0x1E73, 'V'), + (0x1E74, 'M', u'ṵ'), + (0x1E75, 'V'), + (0x1E76, 'M', u'ṷ'), + (0x1E77, 'V'), + (0x1E78, 'M', u'ṹ'), + (0x1E79, 'V'), + (0x1E7A, 'M', u'ṻ'), + (0x1E7B, 'V'), + (0x1E7C, 'M', u'ṽ'), + (0x1E7D, 'V'), + (0x1E7E, 'M', u'ṿ'), + (0x1E7F, 'V'), + (0x1E80, 'M', u'ẁ'), + (0x1E81, 'V'), + (0x1E82, 'M', u'ẃ'), + (0x1E83, 'V'), + (0x1E84, 'M', u'ẅ'), + (0x1E85, 'V'), + (0x1E86, 'M', u'ẇ'), + (0x1E87, 'V'), + (0x1E88, 'M', u'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', u'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', u'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', u'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', u'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', u'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', u'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', u'aʾ'), + (0x1E9B, 'M', u'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', u'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', u'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', u'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', u'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', u'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', u'ẩ'), + ] + +def _seg_18(): + return [ + (0x1EA9, 'V'), + (0x1EAA, 'M', u'ẫ'), + (0x1EAB, 'V'), + (0x1EAC, 'M', u'ậ'), + (0x1EAD, 'V'), + (0x1EAE, 'M', u'ắ'), + (0x1EAF, 'V'), + (0x1EB0, 'M', u'ằ'), + (0x1EB1, 'V'), + (0x1EB2, 'M', u'ẳ'), + (0x1EB3, 'V'), + (0x1EB4, 'M', u'ẵ'), + (0x1EB5, 'V'), + (0x1EB6, 'M', u'ặ'), + (0x1EB7, 'V'), + (0x1EB8, 'M', u'ẹ'), + (0x1EB9, 'V'), + (0x1EBA, 'M', u'ẻ'), + (0x1EBB, 'V'), + (0x1EBC, 'M', u'ẽ'), + (0x1EBD, 'V'), + (0x1EBE, 'M', u'ế'), + (0x1EBF, 'V'), + (0x1EC0, 'M', u'ề'), + (0x1EC1, 'V'), + (0x1EC2, 'M', u'ể'), + (0x1EC3, 'V'), + (0x1EC4, 'M', u'ễ'), + (0x1EC5, 'V'), + (0x1EC6, 'M', u'ệ'), + (0x1EC7, 'V'), + (0x1EC8, 'M', u'ỉ'), + (0x1EC9, 'V'), + (0x1ECA, 'M', u'ị'), + (0x1ECB, 'V'), + (0x1ECC, 'M', u'ọ'), + (0x1ECD, 'V'), + (0x1ECE, 'M', u'ỏ'), + (0x1ECF, 'V'), + (0x1ED0, 'M', u'ố'), + (0x1ED1, 'V'), + (0x1ED2, 'M', u'ồ'), + (0x1ED3, 'V'), + (0x1ED4, 'M', u'ổ'), + (0x1ED5, 'V'), + (0x1ED6, 'M', u'ỗ'), + (0x1ED7, 'V'), + (0x1ED8, 'M', u'ộ'), + (0x1ED9, 'V'), + (0x1EDA, 'M', u'ớ'), + (0x1EDB, 'V'), + (0x1EDC, 'M', u'ờ'), + (0x1EDD, 'V'), + (0x1EDE, 'M', u'ở'), + (0x1EDF, 'V'), + (0x1EE0, 'M', u'ỡ'), + (0x1EE1, 'V'), + (0x1EE2, 'M', u'ợ'), + (0x1EE3, 'V'), + (0x1EE4, 'M', u'ụ'), + (0x1EE5, 'V'), + (0x1EE6, 'M', u'ủ'), + (0x1EE7, 'V'), + (0x1EE8, 'M', u'ứ'), + (0x1EE9, 'V'), + (0x1EEA, 'M', u'ừ'), + (0x1EEB, 'V'), + (0x1EEC, 'M', u'ử'), + (0x1EED, 'V'), + (0x1EEE, 'M', u'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', u'ự'), + (0x1EF1, 'V'), + (0x1EF2, 'M', u'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', u'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', u'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', u'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', u'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', u'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', u'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', u'ἀ'), + (0x1F09, 'M', u'ἁ'), + (0x1F0A, 'M', u'ἂ'), + (0x1F0B, 'M', u'ἃ'), + (0x1F0C, 'M', u'ἄ'), + (0x1F0D, 'M', u'ἅ'), + (0x1F0E, 'M', u'ἆ'), + (0x1F0F, 'M', u'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', u'ἐ'), + (0x1F19, 'M', u'ἑ'), + (0x1F1A, 'M', u'ἒ'), + ] + +def _seg_19(): + return [ + (0x1F1B, 'M', u'ἓ'), + (0x1F1C, 'M', u'ἔ'), + (0x1F1D, 'M', u'ἕ'), + (0x1F1E, 'X'), + (0x1F20, 'V'), + (0x1F28, 'M', u'ἠ'), + (0x1F29, 'M', u'ἡ'), + (0x1F2A, 'M', u'ἢ'), + (0x1F2B, 'M', u'ἣ'), + (0x1F2C, 'M', u'ἤ'), + (0x1F2D, 'M', u'ἥ'), + (0x1F2E, 'M', u'ἦ'), + (0x1F2F, 'M', u'ἧ'), + (0x1F30, 'V'), + (0x1F38, 'M', u'ἰ'), + (0x1F39, 'M', u'ἱ'), + (0x1F3A, 'M', u'ἲ'), + (0x1F3B, 'M', u'ἳ'), + (0x1F3C, 'M', u'ἴ'), + (0x1F3D, 'M', u'ἵ'), + (0x1F3E, 'M', u'ἶ'), + (0x1F3F, 'M', u'ἷ'), + (0x1F40, 'V'), + (0x1F46, 'X'), + (0x1F48, 'M', u'ὀ'), + (0x1F49, 'M', u'ὁ'), + (0x1F4A, 'M', u'ὂ'), + (0x1F4B, 'M', u'ὃ'), + (0x1F4C, 'M', u'ὄ'), + (0x1F4D, 'M', u'ὅ'), + (0x1F4E, 'X'), + (0x1F50, 'V'), + (0x1F58, 'X'), + (0x1F59, 'M', u'ὑ'), + (0x1F5A, 'X'), + (0x1F5B, 'M', u'ὓ'), + (0x1F5C, 'X'), + (0x1F5D, 'M', u'ὕ'), + (0x1F5E, 'X'), + (0x1F5F, 'M', u'ὗ'), + (0x1F60, 'V'), + (0x1F68, 'M', u'ὠ'), + (0x1F69, 'M', u'ὡ'), + (0x1F6A, 'M', u'ὢ'), + (0x1F6B, 'M', u'ὣ'), + (0x1F6C, 'M', u'ὤ'), + (0x1F6D, 'M', u'ὥ'), + (0x1F6E, 'M', u'ὦ'), + (0x1F6F, 'M', u'ὧ'), + (0x1F70, 'V'), + (0x1F71, 'M', u'ά'), + (0x1F72, 'V'), + (0x1F73, 'M', u'έ'), + (0x1F74, 'V'), + (0x1F75, 'M', u'ή'), + (0x1F76, 'V'), + (0x1F77, 'M', u'ί'), + (0x1F78, 'V'), + (0x1F79, 'M', u'ό'), + (0x1F7A, 'V'), + (0x1F7B, 'M', u'ύ'), + (0x1F7C, 'V'), + (0x1F7D, 'M', u'ώ'), + (0x1F7E, 'X'), + (0x1F80, 'M', u'ἀι'), + (0x1F81, 'M', u'ἁι'), + (0x1F82, 'M', u'ἂι'), + (0x1F83, 'M', u'ἃι'), + (0x1F84, 'M', u'ἄι'), + (0x1F85, 'M', u'ἅι'), + (0x1F86, 'M', u'ἆι'), + (0x1F87, 'M', u'ἇι'), + (0x1F88, 'M', u'ἀι'), + (0x1F89, 'M', u'ἁι'), + (0x1F8A, 'M', u'ἂι'), + (0x1F8B, 'M', u'ἃι'), + (0x1F8C, 'M', u'ἄι'), + (0x1F8D, 'M', u'ἅι'), + (0x1F8E, 'M', u'ἆι'), + (0x1F8F, 'M', u'ἇι'), + (0x1F90, 'M', u'ἠι'), + (0x1F91, 'M', u'ἡι'), + (0x1F92, 'M', u'ἢι'), + (0x1F93, 'M', u'ἣι'), + (0x1F94, 'M', u'ἤι'), + (0x1F95, 'M', u'ἥι'), + (0x1F96, 'M', u'ἦι'), + (0x1F97, 'M', u'ἧι'), + (0x1F98, 'M', u'ἠι'), + (0x1F99, 'M', u'ἡι'), + (0x1F9A, 'M', u'ἢι'), + (0x1F9B, 'M', u'ἣι'), + (0x1F9C, 'M', u'ἤι'), + (0x1F9D, 'M', u'ἥι'), + (0x1F9E, 'M', u'ἦι'), + (0x1F9F, 'M', u'ἧι'), + (0x1FA0, 'M', u'ὠι'), + (0x1FA1, 'M', u'ὡι'), + (0x1FA2, 'M', u'ὢι'), + (0x1FA3, 'M', u'ὣι'), + ] + +def _seg_20(): + return [ + (0x1FA4, 'M', u'ὤι'), + (0x1FA5, 'M', u'ὥι'), + (0x1FA6, 'M', u'ὦι'), + (0x1FA7, 'M', u'ὧι'), + (0x1FA8, 'M', u'ὠι'), + (0x1FA9, 'M', u'ὡι'), + (0x1FAA, 'M', u'ὢι'), + (0x1FAB, 'M', u'ὣι'), + (0x1FAC, 'M', u'ὤι'), + (0x1FAD, 'M', u'ὥι'), + (0x1FAE, 'M', u'ὦι'), + (0x1FAF, 'M', u'ὧι'), + (0x1FB0, 'V'), + (0x1FB2, 'M', u'ὰι'), + (0x1FB3, 'M', u'αι'), + (0x1FB4, 'M', u'άι'), + (0x1FB5, 'X'), + (0x1FB6, 'V'), + (0x1FB7, 'M', u'ᾶι'), + (0x1FB8, 'M', u'ᾰ'), + (0x1FB9, 'M', u'ᾱ'), + (0x1FBA, 'M', u'ὰ'), + (0x1FBB, 'M', u'ά'), + (0x1FBC, 'M', u'αι'), + (0x1FBD, '3', u' ̓'), + (0x1FBE, 'M', u'ι'), + (0x1FBF, '3', u' ̓'), + (0x1FC0, '3', u' ͂'), + (0x1FC1, '3', u' ̈͂'), + (0x1FC2, 'M', u'ὴι'), + (0x1FC3, 'M', u'ηι'), + (0x1FC4, 'M', u'ήι'), + (0x1FC5, 'X'), + (0x1FC6, 'V'), + (0x1FC7, 'M', u'ῆι'), + (0x1FC8, 'M', u'ὲ'), + (0x1FC9, 'M', u'έ'), + (0x1FCA, 'M', u'ὴ'), + (0x1FCB, 'M', u'ή'), + (0x1FCC, 'M', u'ηι'), + (0x1FCD, '3', u' ̓̀'), + (0x1FCE, '3', u' ̓́'), + (0x1FCF, '3', u' ̓͂'), + (0x1FD0, 'V'), + (0x1FD3, 'M', u'ΐ'), + (0x1FD4, 'X'), + (0x1FD6, 'V'), + (0x1FD8, 'M', u'ῐ'), + (0x1FD9, 'M', u'ῑ'), + (0x1FDA, 'M', u'ὶ'), + (0x1FDB, 'M', u'ί'), + (0x1FDC, 'X'), + (0x1FDD, '3', u' ̔̀'), + (0x1FDE, '3', u' ̔́'), + (0x1FDF, '3', u' ̔͂'), + (0x1FE0, 'V'), + (0x1FE3, 'M', u'ΰ'), + (0x1FE4, 'V'), + (0x1FE8, 'M', u'ῠ'), + (0x1FE9, 'M', u'ῡ'), + (0x1FEA, 'M', u'ὺ'), + (0x1FEB, 'M', u'ύ'), + (0x1FEC, 'M', u'ῥ'), + (0x1FED, '3', u' ̈̀'), + (0x1FEE, '3', u' ̈́'), + (0x1FEF, '3', u'`'), + (0x1FF0, 'X'), + (0x1FF2, 'M', u'ὼι'), + (0x1FF3, 'M', u'ωι'), + (0x1FF4, 'M', u'ώι'), + (0x1FF5, 'X'), + (0x1FF6, 'V'), + (0x1FF7, 'M', u'ῶι'), + (0x1FF8, 'M', u'ὸ'), + (0x1FF9, 'M', u'ό'), + (0x1FFA, 'M', u'ὼ'), + (0x1FFB, 'M', u'ώ'), + (0x1FFC, 'M', u'ωι'), + (0x1FFD, '3', u' ́'), + (0x1FFE, '3', u' ̔'), + (0x1FFF, 'X'), + (0x2000, '3', u' '), + (0x200B, 'I'), + (0x200C, 'D', u''), + (0x200E, 'X'), + (0x2010, 'V'), + (0x2011, 'M', u'‐'), + (0x2012, 'V'), + (0x2017, '3', u' ̳'), + (0x2018, 'V'), + (0x2024, 'X'), + (0x2027, 'V'), + (0x2028, 'X'), + (0x202F, '3', u' '), + (0x2030, 'V'), + (0x2033, 'M', u'′′'), + (0x2034, 'M', u'′′′'), + (0x2035, 'V'), + (0x2036, 'M', u'‵‵'), + (0x2037, 'M', u'‵‵‵'), + ] + +def _seg_21(): + return [ + (0x2038, 'V'), + (0x203C, '3', u'!!'), + (0x203D, 'V'), + (0x203E, '3', u' ̅'), + (0x203F, 'V'), + (0x2047, '3', u'??'), + (0x2048, '3', u'?!'), + (0x2049, '3', u'!?'), + (0x204A, 'V'), + (0x2057, 'M', u'′′′′'), + (0x2058, 'V'), + (0x205F, '3', u' '), + (0x2060, 'I'), + (0x2061, 'X'), + (0x2064, 'I'), + (0x2065, 'X'), + (0x2070, 'M', u'0'), + (0x2071, 'M', u'i'), + (0x2072, 'X'), + (0x2074, 'M', u'4'), + (0x2075, 'M', u'5'), + (0x2076, 'M', u'6'), + (0x2077, 'M', u'7'), + (0x2078, 'M', u'8'), + (0x2079, 'M', u'9'), + (0x207A, '3', u'+'), + (0x207B, 'M', u'−'), + (0x207C, '3', u'='), + (0x207D, '3', u'('), + (0x207E, '3', u')'), + (0x207F, 'M', u'n'), + (0x2080, 'M', u'0'), + (0x2081, 'M', u'1'), + (0x2082, 'M', u'2'), + (0x2083, 'M', u'3'), + (0x2084, 'M', u'4'), + (0x2085, 'M', u'5'), + (0x2086, 'M', u'6'), + (0x2087, 'M', u'7'), + (0x2088, 'M', u'8'), + (0x2089, 'M', u'9'), + (0x208A, '3', u'+'), + (0x208B, 'M', u'−'), + (0x208C, '3', u'='), + (0x208D, '3', u'('), + (0x208E, '3', u')'), + (0x208F, 'X'), + (0x2090, 'M', u'a'), + (0x2091, 'M', u'e'), + (0x2092, 'M', u'o'), + (0x2093, 'M', u'x'), + (0x2094, 'M', u'ə'), + (0x2095, 'M', u'h'), + (0x2096, 'M', u'k'), + (0x2097, 'M', u'l'), + (0x2098, 'M', u'm'), + (0x2099, 'M', u'n'), + (0x209A, 'M', u'p'), + (0x209B, 'M', u's'), + (0x209C, 'M', u't'), + (0x209D, 'X'), + (0x20A0, 'V'), + (0x20A8, 'M', u'rs'), + (0x20A9, 'V'), + (0x20C0, 'X'), + (0x20D0, 'V'), + (0x20F1, 'X'), + (0x2100, '3', u'a/c'), + (0x2101, '3', u'a/s'), + (0x2102, 'M', u'c'), + (0x2103, 'M', u'°c'), + (0x2104, 'V'), + (0x2105, '3', u'c/o'), + (0x2106, '3', u'c/u'), + (0x2107, 'M', u'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', u'°f'), + (0x210A, 'M', u'g'), + (0x210B, 'M', u'h'), + (0x210F, 'M', u'ħ'), + (0x2110, 'M', u'i'), + (0x2112, 'M', u'l'), + (0x2114, 'V'), + (0x2115, 'M', u'n'), + (0x2116, 'M', u'no'), + (0x2117, 'V'), + (0x2119, 'M', u'p'), + (0x211A, 'M', u'q'), + (0x211B, 'M', u'r'), + (0x211E, 'V'), + (0x2120, 'M', u'sm'), + (0x2121, 'M', u'tel'), + (0x2122, 'M', u'tm'), + (0x2123, 'V'), + (0x2124, 'M', u'z'), + (0x2125, 'V'), + (0x2126, 'M', u'ω'), + (0x2127, 'V'), + (0x2128, 'M', u'z'), + (0x2129, 'V'), + ] + +def _seg_22(): + return [ + (0x212A, 'M', u'k'), + (0x212B, 'M', u'å'), + (0x212C, 'M', u'b'), + (0x212D, 'M', u'c'), + (0x212E, 'V'), + (0x212F, 'M', u'e'), + (0x2131, 'M', u'f'), + (0x2132, 'X'), + (0x2133, 'M', u'm'), + (0x2134, 'M', u'o'), + (0x2135, 'M', u'א'), + (0x2136, 'M', u'ב'), + (0x2137, 'M', u'ג'), + (0x2138, 'M', u'ד'), + (0x2139, 'M', u'i'), + (0x213A, 'V'), + (0x213B, 'M', u'fax'), + (0x213C, 'M', u'π'), + (0x213D, 'M', u'γ'), + (0x213F, 'M', u'π'), + (0x2140, 'M', u'∑'), + (0x2141, 'V'), + (0x2145, 'M', u'd'), + (0x2147, 'M', u'e'), + (0x2148, 'M', u'i'), + (0x2149, 'M', u'j'), + (0x214A, 'V'), + (0x2150, 'M', u'1⁄7'), + (0x2151, 'M', u'1⁄9'), + (0x2152, 'M', u'1⁄10'), + (0x2153, 'M', u'1⁄3'), + (0x2154, 'M', u'2⁄3'), + (0x2155, 'M', u'1⁄5'), + (0x2156, 'M', u'2⁄5'), + (0x2157, 'M', u'3⁄5'), + (0x2158, 'M', u'4⁄5'), + (0x2159, 'M', u'1⁄6'), + (0x215A, 'M', u'5⁄6'), + (0x215B, 'M', u'1⁄8'), + (0x215C, 'M', u'3⁄8'), + (0x215D, 'M', u'5⁄8'), + (0x215E, 'M', u'7⁄8'), + (0x215F, 'M', u'1⁄'), + (0x2160, 'M', u'i'), + (0x2161, 'M', u'ii'), + (0x2162, 'M', u'iii'), + (0x2163, 'M', u'iv'), + (0x2164, 'M', u'v'), + (0x2165, 'M', u'vi'), + (0x2166, 'M', u'vii'), + (0x2167, 'M', u'viii'), + (0x2168, 'M', u'ix'), + (0x2169, 'M', u'x'), + (0x216A, 'M', u'xi'), + (0x216B, 'M', u'xii'), + (0x216C, 'M', u'l'), + (0x216D, 'M', u'c'), + (0x216E, 'M', u'd'), + (0x216F, 'M', u'm'), + (0x2170, 'M', u'i'), + (0x2171, 'M', u'ii'), + (0x2172, 'M', u'iii'), + (0x2173, 'M', u'iv'), + (0x2174, 'M', u'v'), + (0x2175, 'M', u'vi'), + (0x2176, 'M', u'vii'), + (0x2177, 'M', u'viii'), + (0x2178, 'M', u'ix'), + (0x2179, 'M', u'x'), + (0x217A, 'M', u'xi'), + (0x217B, 'M', u'xii'), + (0x217C, 'M', u'l'), + (0x217D, 'M', u'c'), + (0x217E, 'M', u'd'), + (0x217F, 'M', u'm'), + (0x2180, 'V'), + (0x2183, 'X'), + (0x2184, 'V'), + (0x2189, 'M', u'0⁄3'), + (0x218A, 'V'), + (0x218C, 'X'), + (0x2190, 'V'), + (0x222C, 'M', u'∫∫'), + (0x222D, 'M', u'∫∫∫'), + (0x222E, 'V'), + (0x222F, 'M', u'∮∮'), + (0x2230, 'M', u'∮∮∮'), + (0x2231, 'V'), + (0x2260, '3'), + (0x2261, 'V'), + (0x226E, '3'), + (0x2270, 'V'), + (0x2329, 'M', u'〈'), + (0x232A, 'M', u'〉'), + (0x232B, 'V'), + (0x2427, 'X'), + (0x2440, 'V'), + (0x244B, 'X'), + (0x2460, 'M', u'1'), + (0x2461, 'M', u'2'), + ] + +def _seg_23(): + return [ + (0x2462, 'M', u'3'), + (0x2463, 'M', u'4'), + (0x2464, 'M', u'5'), + (0x2465, 'M', u'6'), + (0x2466, 'M', u'7'), + (0x2467, 'M', u'8'), + (0x2468, 'M', u'9'), + (0x2469, 'M', u'10'), + (0x246A, 'M', u'11'), + (0x246B, 'M', u'12'), + (0x246C, 'M', u'13'), + (0x246D, 'M', u'14'), + (0x246E, 'M', u'15'), + (0x246F, 'M', u'16'), + (0x2470, 'M', u'17'), + (0x2471, 'M', u'18'), + (0x2472, 'M', u'19'), + (0x2473, 'M', u'20'), + (0x2474, '3', u'(1)'), + (0x2475, '3', u'(2)'), + (0x2476, '3', u'(3)'), + (0x2477, '3', u'(4)'), + (0x2478, '3', u'(5)'), + (0x2479, '3', u'(6)'), + (0x247A, '3', u'(7)'), + (0x247B, '3', u'(8)'), + (0x247C, '3', u'(9)'), + (0x247D, '3', u'(10)'), + (0x247E, '3', u'(11)'), + (0x247F, '3', u'(12)'), + (0x2480, '3', u'(13)'), + (0x2481, '3', u'(14)'), + (0x2482, '3', u'(15)'), + (0x2483, '3', u'(16)'), + (0x2484, '3', u'(17)'), + (0x2485, '3', u'(18)'), + (0x2486, '3', u'(19)'), + (0x2487, '3', u'(20)'), + (0x2488, 'X'), + (0x249C, '3', u'(a)'), + (0x249D, '3', u'(b)'), + (0x249E, '3', u'(c)'), + (0x249F, '3', u'(d)'), + (0x24A0, '3', u'(e)'), + (0x24A1, '3', u'(f)'), + (0x24A2, '3', u'(g)'), + (0x24A3, '3', u'(h)'), + (0x24A4, '3', u'(i)'), + (0x24A5, '3', u'(j)'), + (0x24A6, '3', u'(k)'), + (0x24A7, '3', u'(l)'), + (0x24A8, '3', u'(m)'), + (0x24A9, '3', u'(n)'), + (0x24AA, '3', u'(o)'), + (0x24AB, '3', u'(p)'), + (0x24AC, '3', u'(q)'), + (0x24AD, '3', u'(r)'), + (0x24AE, '3', u'(s)'), + (0x24AF, '3', u'(t)'), + (0x24B0, '3', u'(u)'), + (0x24B1, '3', u'(v)'), + (0x24B2, '3', u'(w)'), + (0x24B3, '3', u'(x)'), + (0x24B4, '3', u'(y)'), + (0x24B5, '3', u'(z)'), + (0x24B6, 'M', u'a'), + (0x24B7, 'M', u'b'), + (0x24B8, 'M', u'c'), + (0x24B9, 'M', u'd'), + (0x24BA, 'M', u'e'), + (0x24BB, 'M', u'f'), + (0x24BC, 'M', u'g'), + (0x24BD, 'M', u'h'), + (0x24BE, 'M', u'i'), + (0x24BF, 'M', u'j'), + (0x24C0, 'M', u'k'), + (0x24C1, 'M', u'l'), + (0x24C2, 'M', u'm'), + (0x24C3, 'M', u'n'), + (0x24C4, 'M', u'o'), + (0x24C5, 'M', u'p'), + (0x24C6, 'M', u'q'), + (0x24C7, 'M', u'r'), + (0x24C8, 'M', u's'), + (0x24C9, 'M', u't'), + (0x24CA, 'M', u'u'), + (0x24CB, 'M', u'v'), + (0x24CC, 'M', u'w'), + (0x24CD, 'M', u'x'), + (0x24CE, 'M', u'y'), + (0x24CF, 'M', u'z'), + (0x24D0, 'M', u'a'), + (0x24D1, 'M', u'b'), + (0x24D2, 'M', u'c'), + (0x24D3, 'M', u'd'), + (0x24D4, 'M', u'e'), + (0x24D5, 'M', u'f'), + (0x24D6, 'M', u'g'), + (0x24D7, 'M', u'h'), + (0x24D8, 'M', u'i'), + ] + +def _seg_24(): + return [ + (0x24D9, 'M', u'j'), + (0x24DA, 'M', u'k'), + (0x24DB, 'M', u'l'), + (0x24DC, 'M', u'm'), + (0x24DD, 'M', u'n'), + (0x24DE, 'M', u'o'), + (0x24DF, 'M', u'p'), + (0x24E0, 'M', u'q'), + (0x24E1, 'M', u'r'), + (0x24E2, 'M', u's'), + (0x24E3, 'M', u't'), + (0x24E4, 'M', u'u'), + (0x24E5, 'M', u'v'), + (0x24E6, 'M', u'w'), + (0x24E7, 'M', u'x'), + (0x24E8, 'M', u'y'), + (0x24E9, 'M', u'z'), + (0x24EA, 'M', u'0'), + (0x24EB, 'V'), + (0x2A0C, 'M', u'∫∫∫∫'), + (0x2A0D, 'V'), + (0x2A74, '3', u'::='), + (0x2A75, '3', u'=='), + (0x2A76, '3', u'==='), + (0x2A77, 'V'), + (0x2ADC, 'M', u'⫝̸'), + (0x2ADD, 'V'), + (0x2B74, 'X'), + (0x2B76, 'V'), + (0x2B96, 'X'), + (0x2B98, 'V'), + (0x2BC9, 'X'), + (0x2BCA, 'V'), + (0x2BFF, 'X'), + (0x2C00, 'M', u'ⰰ'), + (0x2C01, 'M', u'ⰱ'), + (0x2C02, 'M', u'ⰲ'), + (0x2C03, 'M', u'ⰳ'), + (0x2C04, 'M', u'ⰴ'), + (0x2C05, 'M', u'ⰵ'), + (0x2C06, 'M', u'ⰶ'), + (0x2C07, 'M', u'ⰷ'), + (0x2C08, 'M', u'ⰸ'), + (0x2C09, 'M', u'ⰹ'), + (0x2C0A, 'M', u'ⰺ'), + (0x2C0B, 'M', u'ⰻ'), + (0x2C0C, 'M', u'ⰼ'), + (0x2C0D, 'M', u'ⰽ'), + (0x2C0E, 'M', u'ⰾ'), + (0x2C0F, 'M', u'ⰿ'), + (0x2C10, 'M', u'ⱀ'), + (0x2C11, 'M', u'ⱁ'), + (0x2C12, 'M', u'ⱂ'), + (0x2C13, 'M', u'ⱃ'), + (0x2C14, 'M', u'ⱄ'), + (0x2C15, 'M', u'ⱅ'), + (0x2C16, 'M', u'ⱆ'), + (0x2C17, 'M', u'ⱇ'), + (0x2C18, 'M', u'ⱈ'), + (0x2C19, 'M', u'ⱉ'), + (0x2C1A, 'M', u'ⱊ'), + (0x2C1B, 'M', u'ⱋ'), + (0x2C1C, 'M', u'ⱌ'), + (0x2C1D, 'M', u'ⱍ'), + (0x2C1E, 'M', u'ⱎ'), + (0x2C1F, 'M', u'ⱏ'), + (0x2C20, 'M', u'ⱐ'), + (0x2C21, 'M', u'ⱑ'), + (0x2C22, 'M', u'ⱒ'), + (0x2C23, 'M', u'ⱓ'), + (0x2C24, 'M', u'ⱔ'), + (0x2C25, 'M', u'ⱕ'), + (0x2C26, 'M', u'ⱖ'), + (0x2C27, 'M', u'ⱗ'), + (0x2C28, 'M', u'ⱘ'), + (0x2C29, 'M', u'ⱙ'), + (0x2C2A, 'M', u'ⱚ'), + (0x2C2B, 'M', u'ⱛ'), + (0x2C2C, 'M', u'ⱜ'), + (0x2C2D, 'M', u'ⱝ'), + (0x2C2E, 'M', u'ⱞ'), + (0x2C2F, 'X'), + (0x2C30, 'V'), + (0x2C5F, 'X'), + (0x2C60, 'M', u'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', u'ɫ'), + (0x2C63, 'M', u'ᵽ'), + (0x2C64, 'M', u'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', u'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', u'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', u'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', u'ɑ'), + (0x2C6E, 'M', u'ɱ'), + (0x2C6F, 'M', u'ɐ'), + (0x2C70, 'M', u'ɒ'), + ] + +def _seg_25(): + return [ + (0x2C71, 'V'), + (0x2C72, 'M', u'ⱳ'), + (0x2C73, 'V'), + (0x2C75, 'M', u'ⱶ'), + (0x2C76, 'V'), + (0x2C7C, 'M', u'j'), + (0x2C7D, 'M', u'v'), + (0x2C7E, 'M', u'ȿ'), + (0x2C7F, 'M', u'ɀ'), + (0x2C80, 'M', u'ⲁ'), + (0x2C81, 'V'), + (0x2C82, 'M', u'ⲃ'), + (0x2C83, 'V'), + (0x2C84, 'M', u'ⲅ'), + (0x2C85, 'V'), + (0x2C86, 'M', u'ⲇ'), + (0x2C87, 'V'), + (0x2C88, 'M', u'ⲉ'), + (0x2C89, 'V'), + (0x2C8A, 'M', u'ⲋ'), + (0x2C8B, 'V'), + (0x2C8C, 'M', u'ⲍ'), + (0x2C8D, 'V'), + (0x2C8E, 'M', u'ⲏ'), + (0x2C8F, 'V'), + (0x2C90, 'M', u'ⲑ'), + (0x2C91, 'V'), + (0x2C92, 'M', u'ⲓ'), + (0x2C93, 'V'), + (0x2C94, 'M', u'ⲕ'), + (0x2C95, 'V'), + (0x2C96, 'M', u'ⲗ'), + (0x2C97, 'V'), + (0x2C98, 'M', u'ⲙ'), + (0x2C99, 'V'), + (0x2C9A, 'M', u'ⲛ'), + (0x2C9B, 'V'), + (0x2C9C, 'M', u'ⲝ'), + (0x2C9D, 'V'), + (0x2C9E, 'M', u'ⲟ'), + (0x2C9F, 'V'), + (0x2CA0, 'M', u'ⲡ'), + (0x2CA1, 'V'), + (0x2CA2, 'M', u'ⲣ'), + (0x2CA3, 'V'), + (0x2CA4, 'M', u'ⲥ'), + (0x2CA5, 'V'), + (0x2CA6, 'M', u'ⲧ'), + (0x2CA7, 'V'), + (0x2CA8, 'M', u'ⲩ'), + (0x2CA9, 'V'), + (0x2CAA, 'M', u'ⲫ'), + (0x2CAB, 'V'), + (0x2CAC, 'M', u'ⲭ'), + (0x2CAD, 'V'), + (0x2CAE, 'M', u'ⲯ'), + (0x2CAF, 'V'), + (0x2CB0, 'M', u'ⲱ'), + (0x2CB1, 'V'), + (0x2CB2, 'M', u'ⲳ'), + (0x2CB3, 'V'), + (0x2CB4, 'M', u'ⲵ'), + (0x2CB5, 'V'), + (0x2CB6, 'M', u'ⲷ'), + (0x2CB7, 'V'), + (0x2CB8, 'M', u'ⲹ'), + (0x2CB9, 'V'), + (0x2CBA, 'M', u'ⲻ'), + (0x2CBB, 'V'), + (0x2CBC, 'M', u'ⲽ'), + (0x2CBD, 'V'), + (0x2CBE, 'M', u'ⲿ'), + (0x2CBF, 'V'), + (0x2CC0, 'M', u'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', u'ⳃ'), + (0x2CC3, 'V'), + (0x2CC4, 'M', u'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', u'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', u'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', u'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', u'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', u'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', u'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', u'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', u'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', u'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', u'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', u'ⳛ'), + ] + +def _seg_26(): + return [ + (0x2CDB, 'V'), + (0x2CDC, 'M', u'ⳝ'), + (0x2CDD, 'V'), + (0x2CDE, 'M', u'ⳟ'), + (0x2CDF, 'V'), + (0x2CE0, 'M', u'ⳡ'), + (0x2CE1, 'V'), + (0x2CE2, 'M', u'ⳣ'), + (0x2CE3, 'V'), + (0x2CEB, 'M', u'ⳬ'), + (0x2CEC, 'V'), + (0x2CED, 'M', u'ⳮ'), + (0x2CEE, 'V'), + (0x2CF2, 'M', u'ⳳ'), + (0x2CF3, 'V'), + (0x2CF4, 'X'), + (0x2CF9, 'V'), + (0x2D26, 'X'), + (0x2D27, 'V'), + (0x2D28, 'X'), + (0x2D2D, 'V'), + (0x2D2E, 'X'), + (0x2D30, 'V'), + (0x2D68, 'X'), + (0x2D6F, 'M', u'ⵡ'), + (0x2D70, 'V'), + (0x2D71, 'X'), + (0x2D7F, 'V'), + (0x2D97, 'X'), + (0x2DA0, 'V'), + (0x2DA7, 'X'), + (0x2DA8, 'V'), + (0x2DAF, 'X'), + (0x2DB0, 'V'), + (0x2DB7, 'X'), + (0x2DB8, 'V'), + (0x2DBF, 'X'), + (0x2DC0, 'V'), + (0x2DC7, 'X'), + (0x2DC8, 'V'), + (0x2DCF, 'X'), + (0x2DD0, 'V'), + (0x2DD7, 'X'), + (0x2DD8, 'V'), + (0x2DDF, 'X'), + (0x2DE0, 'V'), + (0x2E4F, 'X'), + (0x2E80, 'V'), + (0x2E9A, 'X'), + (0x2E9B, 'V'), + (0x2E9F, 'M', u'母'), + (0x2EA0, 'V'), + (0x2EF3, 'M', u'龟'), + (0x2EF4, 'X'), + (0x2F00, 'M', u'一'), + (0x2F01, 'M', u'丨'), + (0x2F02, 'M', u'丶'), + (0x2F03, 'M', u'丿'), + (0x2F04, 'M', u'乙'), + (0x2F05, 'M', u'亅'), + (0x2F06, 'M', u'二'), + (0x2F07, 'M', u'亠'), + (0x2F08, 'M', u'人'), + (0x2F09, 'M', u'儿'), + (0x2F0A, 'M', u'入'), + (0x2F0B, 'M', u'八'), + (0x2F0C, 'M', u'冂'), + (0x2F0D, 'M', u'冖'), + (0x2F0E, 'M', u'冫'), + (0x2F0F, 'M', u'几'), + (0x2F10, 'M', u'凵'), + (0x2F11, 'M', u'刀'), + (0x2F12, 'M', u'力'), + (0x2F13, 'M', u'勹'), + (0x2F14, 'M', u'匕'), + (0x2F15, 'M', u'匚'), + (0x2F16, 'M', u'匸'), + (0x2F17, 'M', u'十'), + (0x2F18, 'M', u'卜'), + (0x2F19, 'M', u'卩'), + (0x2F1A, 'M', u'厂'), + (0x2F1B, 'M', u'厶'), + (0x2F1C, 'M', u'又'), + (0x2F1D, 'M', u'口'), + (0x2F1E, 'M', u'囗'), + (0x2F1F, 'M', u'土'), + (0x2F20, 'M', u'士'), + (0x2F21, 'M', u'夂'), + (0x2F22, 'M', u'夊'), + (0x2F23, 'M', u'夕'), + (0x2F24, 'M', u'大'), + (0x2F25, 'M', u'女'), + (0x2F26, 'M', u'子'), + (0x2F27, 'M', u'宀'), + (0x2F28, 'M', u'寸'), + (0x2F29, 'M', u'小'), + (0x2F2A, 'M', u'尢'), + (0x2F2B, 'M', u'尸'), + (0x2F2C, 'M', u'屮'), + (0x2F2D, 'M', u'山'), + ] + +def _seg_27(): + return [ + (0x2F2E, 'M', u'巛'), + (0x2F2F, 'M', u'工'), + (0x2F30, 'M', u'己'), + (0x2F31, 'M', u'巾'), + (0x2F32, 'M', u'干'), + (0x2F33, 'M', u'幺'), + (0x2F34, 'M', u'广'), + (0x2F35, 'M', u'廴'), + (0x2F36, 'M', u'廾'), + (0x2F37, 'M', u'弋'), + (0x2F38, 'M', u'弓'), + (0x2F39, 'M', u'彐'), + (0x2F3A, 'M', u'彡'), + (0x2F3B, 'M', u'彳'), + (0x2F3C, 'M', u'心'), + (0x2F3D, 'M', u'戈'), + (0x2F3E, 'M', u'戶'), + (0x2F3F, 'M', u'手'), + (0x2F40, 'M', u'支'), + (0x2F41, 'M', u'攴'), + (0x2F42, 'M', u'文'), + (0x2F43, 'M', u'斗'), + (0x2F44, 'M', u'斤'), + (0x2F45, 'M', u'方'), + (0x2F46, 'M', u'无'), + (0x2F47, 'M', u'日'), + (0x2F48, 'M', u'曰'), + (0x2F49, 'M', u'月'), + (0x2F4A, 'M', u'木'), + (0x2F4B, 'M', u'欠'), + (0x2F4C, 'M', u'止'), + (0x2F4D, 'M', u'歹'), + (0x2F4E, 'M', u'殳'), + (0x2F4F, 'M', u'毋'), + (0x2F50, 'M', u'比'), + (0x2F51, 'M', u'毛'), + (0x2F52, 'M', u'氏'), + (0x2F53, 'M', u'气'), + (0x2F54, 'M', u'水'), + (0x2F55, 'M', u'火'), + (0x2F56, 'M', u'爪'), + (0x2F57, 'M', u'父'), + (0x2F58, 'M', u'爻'), + (0x2F59, 'M', u'爿'), + (0x2F5A, 'M', u'片'), + (0x2F5B, 'M', u'牙'), + (0x2F5C, 'M', u'牛'), + (0x2F5D, 'M', u'犬'), + (0x2F5E, 'M', u'玄'), + (0x2F5F, 'M', u'玉'), + (0x2F60, 'M', u'瓜'), + (0x2F61, 'M', u'瓦'), + (0x2F62, 'M', u'甘'), + (0x2F63, 'M', u'生'), + (0x2F64, 'M', u'用'), + (0x2F65, 'M', u'田'), + (0x2F66, 'M', u'疋'), + (0x2F67, 'M', u'疒'), + (0x2F68, 'M', u'癶'), + (0x2F69, 'M', u'白'), + (0x2F6A, 'M', u'皮'), + (0x2F6B, 'M', u'皿'), + (0x2F6C, 'M', u'目'), + (0x2F6D, 'M', u'矛'), + (0x2F6E, 'M', u'矢'), + (0x2F6F, 'M', u'石'), + (0x2F70, 'M', u'示'), + (0x2F71, 'M', u'禸'), + (0x2F72, 'M', u'禾'), + (0x2F73, 'M', u'穴'), + (0x2F74, 'M', u'立'), + (0x2F75, 'M', u'竹'), + (0x2F76, 'M', u'米'), + (0x2F77, 'M', u'糸'), + (0x2F78, 'M', u'缶'), + (0x2F79, 'M', u'网'), + (0x2F7A, 'M', u'羊'), + (0x2F7B, 'M', u'羽'), + (0x2F7C, 'M', u'老'), + (0x2F7D, 'M', u'而'), + (0x2F7E, 'M', u'耒'), + (0x2F7F, 'M', u'耳'), + (0x2F80, 'M', u'聿'), + (0x2F81, 'M', u'肉'), + (0x2F82, 'M', u'臣'), + (0x2F83, 'M', u'自'), + (0x2F84, 'M', u'至'), + (0x2F85, 'M', u'臼'), + (0x2F86, 'M', u'舌'), + (0x2F87, 'M', u'舛'), + (0x2F88, 'M', u'舟'), + (0x2F89, 'M', u'艮'), + (0x2F8A, 'M', u'色'), + (0x2F8B, 'M', u'艸'), + (0x2F8C, 'M', u'虍'), + (0x2F8D, 'M', u'虫'), + (0x2F8E, 'M', u'血'), + (0x2F8F, 'M', u'行'), + (0x2F90, 'M', u'衣'), + (0x2F91, 'M', u'襾'), + ] + +def _seg_28(): + return [ + (0x2F92, 'M', u'見'), + (0x2F93, 'M', u'角'), + (0x2F94, 'M', u'言'), + (0x2F95, 'M', u'谷'), + (0x2F96, 'M', u'豆'), + (0x2F97, 'M', u'豕'), + (0x2F98, 'M', u'豸'), + (0x2F99, 'M', u'貝'), + (0x2F9A, 'M', u'赤'), + (0x2F9B, 'M', u'走'), + (0x2F9C, 'M', u'足'), + (0x2F9D, 'M', u'身'), + (0x2F9E, 'M', u'車'), + (0x2F9F, 'M', u'辛'), + (0x2FA0, 'M', u'辰'), + (0x2FA1, 'M', u'辵'), + (0x2FA2, 'M', u'邑'), + (0x2FA3, 'M', u'酉'), + (0x2FA4, 'M', u'釆'), + (0x2FA5, 'M', u'里'), + (0x2FA6, 'M', u'金'), + (0x2FA7, 'M', u'長'), + (0x2FA8, 'M', u'門'), + (0x2FA9, 'M', u'阜'), + (0x2FAA, 'M', u'隶'), + (0x2FAB, 'M', u'隹'), + (0x2FAC, 'M', u'雨'), + (0x2FAD, 'M', u'靑'), + (0x2FAE, 'M', u'非'), + (0x2FAF, 'M', u'面'), + (0x2FB0, 'M', u'革'), + (0x2FB1, 'M', u'韋'), + (0x2FB2, 'M', u'韭'), + (0x2FB3, 'M', u'音'), + (0x2FB4, 'M', u'頁'), + (0x2FB5, 'M', u'風'), + (0x2FB6, 'M', u'飛'), + (0x2FB7, 'M', u'食'), + (0x2FB8, 'M', u'首'), + (0x2FB9, 'M', u'香'), + (0x2FBA, 'M', u'馬'), + (0x2FBB, 'M', u'骨'), + (0x2FBC, 'M', u'高'), + (0x2FBD, 'M', u'髟'), + (0x2FBE, 'M', u'鬥'), + (0x2FBF, 'M', u'鬯'), + (0x2FC0, 'M', u'鬲'), + (0x2FC1, 'M', u'鬼'), + (0x2FC2, 'M', u'魚'), + (0x2FC3, 'M', u'鳥'), + (0x2FC4, 'M', u'鹵'), + (0x2FC5, 'M', u'鹿'), + (0x2FC6, 'M', u'麥'), + (0x2FC7, 'M', u'麻'), + (0x2FC8, 'M', u'黃'), + (0x2FC9, 'M', u'黍'), + (0x2FCA, 'M', u'黑'), + (0x2FCB, 'M', u'黹'), + (0x2FCC, 'M', u'黽'), + (0x2FCD, 'M', u'鼎'), + (0x2FCE, 'M', u'鼓'), + (0x2FCF, 'M', u'鼠'), + (0x2FD0, 'M', u'鼻'), + (0x2FD1, 'M', u'齊'), + (0x2FD2, 'M', u'齒'), + (0x2FD3, 'M', u'龍'), + (0x2FD4, 'M', u'龜'), + (0x2FD5, 'M', u'龠'), + (0x2FD6, 'X'), + (0x3000, '3', u' '), + (0x3001, 'V'), + (0x3002, 'M', u'.'), + (0x3003, 'V'), + (0x3036, 'M', u'〒'), + (0x3037, 'V'), + (0x3038, 'M', u'十'), + (0x3039, 'M', u'卄'), + (0x303A, 'M', u'卅'), + (0x303B, 'V'), + (0x3040, 'X'), + (0x3041, 'V'), + (0x3097, 'X'), + (0x3099, 'V'), + (0x309B, '3', u' ゙'), + (0x309C, '3', u' ゚'), + (0x309D, 'V'), + (0x309F, 'M', u'より'), + (0x30A0, 'V'), + (0x30FF, 'M', u'コト'), + (0x3100, 'X'), + (0x3105, 'V'), + (0x3130, 'X'), + (0x3131, 'M', u'ᄀ'), + (0x3132, 'M', u'ᄁ'), + (0x3133, 'M', u'ᆪ'), + (0x3134, 'M', u'ᄂ'), + (0x3135, 'M', u'ᆬ'), + (0x3136, 'M', u'ᆭ'), + (0x3137, 'M', u'ᄃ'), + (0x3138, 'M', u'ᄄ'), + ] + +def _seg_29(): + return [ + (0x3139, 'M', u'ᄅ'), + (0x313A, 'M', u'ᆰ'), + (0x313B, 'M', u'ᆱ'), + (0x313C, 'M', u'ᆲ'), + (0x313D, 'M', u'ᆳ'), + (0x313E, 'M', u'ᆴ'), + (0x313F, 'M', u'ᆵ'), + (0x3140, 'M', u'ᄚ'), + (0x3141, 'M', u'ᄆ'), + (0x3142, 'M', u'ᄇ'), + (0x3143, 'M', u'ᄈ'), + (0x3144, 'M', u'ᄡ'), + (0x3145, 'M', u'ᄉ'), + (0x3146, 'M', u'ᄊ'), + (0x3147, 'M', u'ᄋ'), + (0x3148, 'M', u'ᄌ'), + (0x3149, 'M', u'ᄍ'), + (0x314A, 'M', u'ᄎ'), + (0x314B, 'M', u'ᄏ'), + (0x314C, 'M', u'ᄐ'), + (0x314D, 'M', u'ᄑ'), + (0x314E, 'M', u'ᄒ'), + (0x314F, 'M', u'ᅡ'), + (0x3150, 'M', u'ᅢ'), + (0x3151, 'M', u'ᅣ'), + (0x3152, 'M', u'ᅤ'), + (0x3153, 'M', u'ᅥ'), + (0x3154, 'M', u'ᅦ'), + (0x3155, 'M', u'ᅧ'), + (0x3156, 'M', u'ᅨ'), + (0x3157, 'M', u'ᅩ'), + (0x3158, 'M', u'ᅪ'), + (0x3159, 'M', u'ᅫ'), + (0x315A, 'M', u'ᅬ'), + (0x315B, 'M', u'ᅭ'), + (0x315C, 'M', u'ᅮ'), + (0x315D, 'M', u'ᅯ'), + (0x315E, 'M', u'ᅰ'), + (0x315F, 'M', u'ᅱ'), + (0x3160, 'M', u'ᅲ'), + (0x3161, 'M', u'ᅳ'), + (0x3162, 'M', u'ᅴ'), + (0x3163, 'M', u'ᅵ'), + (0x3164, 'X'), + (0x3165, 'M', u'ᄔ'), + (0x3166, 'M', u'ᄕ'), + (0x3167, 'M', u'ᇇ'), + (0x3168, 'M', u'ᇈ'), + (0x3169, 'M', u'ᇌ'), + (0x316A, 'M', u'ᇎ'), + (0x316B, 'M', u'ᇓ'), + (0x316C, 'M', u'ᇗ'), + (0x316D, 'M', u'ᇙ'), + (0x316E, 'M', u'ᄜ'), + (0x316F, 'M', u'ᇝ'), + (0x3170, 'M', u'ᇟ'), + (0x3171, 'M', u'ᄝ'), + (0x3172, 'M', u'ᄞ'), + (0x3173, 'M', u'ᄠ'), + (0x3174, 'M', u'ᄢ'), + (0x3175, 'M', u'ᄣ'), + (0x3176, 'M', u'ᄧ'), + (0x3177, 'M', u'ᄩ'), + (0x3178, 'M', u'ᄫ'), + (0x3179, 'M', u'ᄬ'), + (0x317A, 'M', u'ᄭ'), + (0x317B, 'M', u'ᄮ'), + (0x317C, 'M', u'ᄯ'), + (0x317D, 'M', u'ᄲ'), + (0x317E, 'M', u'ᄶ'), + (0x317F, 'M', u'ᅀ'), + (0x3180, 'M', u'ᅇ'), + (0x3181, 'M', u'ᅌ'), + (0x3182, 'M', u'ᇱ'), + (0x3183, 'M', u'ᇲ'), + (0x3184, 'M', u'ᅗ'), + (0x3185, 'M', u'ᅘ'), + (0x3186, 'M', u'ᅙ'), + (0x3187, 'M', u'ᆄ'), + (0x3188, 'M', u'ᆅ'), + (0x3189, 'M', u'ᆈ'), + (0x318A, 'M', u'ᆑ'), + (0x318B, 'M', u'ᆒ'), + (0x318C, 'M', u'ᆔ'), + (0x318D, 'M', u'ᆞ'), + (0x318E, 'M', u'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', u'一'), + (0x3193, 'M', u'二'), + (0x3194, 'M', u'三'), + (0x3195, 'M', u'四'), + (0x3196, 'M', u'上'), + (0x3197, 'M', u'中'), + (0x3198, 'M', u'下'), + (0x3199, 'M', u'甲'), + (0x319A, 'M', u'乙'), + (0x319B, 'M', u'丙'), + (0x319C, 'M', u'丁'), + (0x319D, 'M', u'天'), + ] + +def _seg_30(): + return [ + (0x319E, 'M', u'地'), + (0x319F, 'M', u'人'), + (0x31A0, 'V'), + (0x31BB, 'X'), + (0x31C0, 'V'), + (0x31E4, 'X'), + (0x31F0, 'V'), + (0x3200, '3', u'(ᄀ)'), + (0x3201, '3', u'(ᄂ)'), + (0x3202, '3', u'(ᄃ)'), + (0x3203, '3', u'(ᄅ)'), + (0x3204, '3', u'(ᄆ)'), + (0x3205, '3', u'(ᄇ)'), + (0x3206, '3', u'(ᄉ)'), + (0x3207, '3', u'(ᄋ)'), + (0x3208, '3', u'(ᄌ)'), + (0x3209, '3', u'(ᄎ)'), + (0x320A, '3', u'(ᄏ)'), + (0x320B, '3', u'(ᄐ)'), + (0x320C, '3', u'(ᄑ)'), + (0x320D, '3', u'(ᄒ)'), + (0x320E, '3', u'(가)'), + (0x320F, '3', u'(나)'), + (0x3210, '3', u'(다)'), + (0x3211, '3', u'(라)'), + (0x3212, '3', u'(마)'), + (0x3213, '3', u'(바)'), + (0x3214, '3', u'(사)'), + (0x3215, '3', u'(아)'), + (0x3216, '3', u'(자)'), + (0x3217, '3', u'(차)'), + (0x3218, '3', u'(카)'), + (0x3219, '3', u'(타)'), + (0x321A, '3', u'(파)'), + (0x321B, '3', u'(하)'), + (0x321C, '3', u'(주)'), + (0x321D, '3', u'(오전)'), + (0x321E, '3', u'(오후)'), + (0x321F, 'X'), + (0x3220, '3', u'(一)'), + (0x3221, '3', u'(二)'), + (0x3222, '3', u'(三)'), + (0x3223, '3', u'(四)'), + (0x3224, '3', u'(五)'), + (0x3225, '3', u'(六)'), + (0x3226, '3', u'(七)'), + (0x3227, '3', u'(八)'), + (0x3228, '3', u'(九)'), + (0x3229, '3', u'(十)'), + (0x322A, '3', u'(月)'), + (0x322B, '3', u'(火)'), + (0x322C, '3', u'(水)'), + (0x322D, '3', u'(木)'), + (0x322E, '3', u'(金)'), + (0x322F, '3', u'(土)'), + (0x3230, '3', u'(日)'), + (0x3231, '3', u'(株)'), + (0x3232, '3', u'(有)'), + (0x3233, '3', u'(社)'), + (0x3234, '3', u'(名)'), + (0x3235, '3', u'(特)'), + (0x3236, '3', u'(財)'), + (0x3237, '3', u'(祝)'), + (0x3238, '3', u'(労)'), + (0x3239, '3', u'(代)'), + (0x323A, '3', u'(呼)'), + (0x323B, '3', u'(学)'), + (0x323C, '3', u'(監)'), + (0x323D, '3', u'(企)'), + (0x323E, '3', u'(資)'), + (0x323F, '3', u'(協)'), + (0x3240, '3', u'(祭)'), + (0x3241, '3', u'(休)'), + (0x3242, '3', u'(自)'), + (0x3243, '3', u'(至)'), + (0x3244, 'M', u'問'), + (0x3245, 'M', u'幼'), + (0x3246, 'M', u'文'), + (0x3247, 'M', u'箏'), + (0x3248, 'V'), + (0x3250, 'M', u'pte'), + (0x3251, 'M', u'21'), + (0x3252, 'M', u'22'), + (0x3253, 'M', u'23'), + (0x3254, 'M', u'24'), + (0x3255, 'M', u'25'), + (0x3256, 'M', u'26'), + (0x3257, 'M', u'27'), + (0x3258, 'M', u'28'), + (0x3259, 'M', u'29'), + (0x325A, 'M', u'30'), + (0x325B, 'M', u'31'), + (0x325C, 'M', u'32'), + (0x325D, 'M', u'33'), + (0x325E, 'M', u'34'), + (0x325F, 'M', u'35'), + (0x3260, 'M', u'ᄀ'), + (0x3261, 'M', u'ᄂ'), + (0x3262, 'M', u'ᄃ'), + (0x3263, 'M', u'ᄅ'), + ] + +def _seg_31(): + return [ + (0x3264, 'M', u'ᄆ'), + (0x3265, 'M', u'ᄇ'), + (0x3266, 'M', u'ᄉ'), + (0x3267, 'M', u'ᄋ'), + (0x3268, 'M', u'ᄌ'), + (0x3269, 'M', u'ᄎ'), + (0x326A, 'M', u'ᄏ'), + (0x326B, 'M', u'ᄐ'), + (0x326C, 'M', u'ᄑ'), + (0x326D, 'M', u'ᄒ'), + (0x326E, 'M', u'가'), + (0x326F, 'M', u'나'), + (0x3270, 'M', u'다'), + (0x3271, 'M', u'라'), + (0x3272, 'M', u'마'), + (0x3273, 'M', u'바'), + (0x3274, 'M', u'사'), + (0x3275, 'M', u'아'), + (0x3276, 'M', u'자'), + (0x3277, 'M', u'차'), + (0x3278, 'M', u'카'), + (0x3279, 'M', u'타'), + (0x327A, 'M', u'파'), + (0x327B, 'M', u'하'), + (0x327C, 'M', u'참고'), + (0x327D, 'M', u'주의'), + (0x327E, 'M', u'우'), + (0x327F, 'V'), + (0x3280, 'M', u'一'), + (0x3281, 'M', u'二'), + (0x3282, 'M', u'三'), + (0x3283, 'M', u'四'), + (0x3284, 'M', u'五'), + (0x3285, 'M', u'六'), + (0x3286, 'M', u'七'), + (0x3287, 'M', u'八'), + (0x3288, 'M', u'九'), + (0x3289, 'M', u'十'), + (0x328A, 'M', u'月'), + (0x328B, 'M', u'火'), + (0x328C, 'M', u'水'), + (0x328D, 'M', u'木'), + (0x328E, 'M', u'金'), + (0x328F, 'M', u'土'), + (0x3290, 'M', u'日'), + (0x3291, 'M', u'株'), + (0x3292, 'M', u'有'), + (0x3293, 'M', u'社'), + (0x3294, 'M', u'名'), + (0x3295, 'M', u'特'), + (0x3296, 'M', u'財'), + (0x3297, 'M', u'祝'), + (0x3298, 'M', u'労'), + (0x3299, 'M', u'秘'), + (0x329A, 'M', u'男'), + (0x329B, 'M', u'女'), + (0x329C, 'M', u'適'), + (0x329D, 'M', u'優'), + (0x329E, 'M', u'印'), + (0x329F, 'M', u'注'), + (0x32A0, 'M', u'項'), + (0x32A1, 'M', u'休'), + (0x32A2, 'M', u'写'), + (0x32A3, 'M', u'正'), + (0x32A4, 'M', u'上'), + (0x32A5, 'M', u'中'), + (0x32A6, 'M', u'下'), + (0x32A7, 'M', u'左'), + (0x32A8, 'M', u'右'), + (0x32A9, 'M', u'医'), + (0x32AA, 'M', u'宗'), + (0x32AB, 'M', u'学'), + (0x32AC, 'M', u'監'), + (0x32AD, 'M', u'企'), + (0x32AE, 'M', u'資'), + (0x32AF, 'M', u'協'), + (0x32B0, 'M', u'夜'), + (0x32B1, 'M', u'36'), + (0x32B2, 'M', u'37'), + (0x32B3, 'M', u'38'), + (0x32B4, 'M', u'39'), + (0x32B5, 'M', u'40'), + (0x32B6, 'M', u'41'), + (0x32B7, 'M', u'42'), + (0x32B8, 'M', u'43'), + (0x32B9, 'M', u'44'), + (0x32BA, 'M', u'45'), + (0x32BB, 'M', u'46'), + (0x32BC, 'M', u'47'), + (0x32BD, 'M', u'48'), + (0x32BE, 'M', u'49'), + (0x32BF, 'M', u'50'), + (0x32C0, 'M', u'1月'), + (0x32C1, 'M', u'2月'), + (0x32C2, 'M', u'3月'), + (0x32C3, 'M', u'4月'), + (0x32C4, 'M', u'5月'), + (0x32C5, 'M', u'6月'), + (0x32C6, 'M', u'7月'), + (0x32C7, 'M', u'8月'), + ] + +def _seg_32(): + return [ + (0x32C8, 'M', u'9月'), + (0x32C9, 'M', u'10月'), + (0x32CA, 'M', u'11月'), + (0x32CB, 'M', u'12月'), + (0x32CC, 'M', u'hg'), + (0x32CD, 'M', u'erg'), + (0x32CE, 'M', u'ev'), + (0x32CF, 'M', u'ltd'), + (0x32D0, 'M', u'ア'), + (0x32D1, 'M', u'イ'), + (0x32D2, 'M', u'ウ'), + (0x32D3, 'M', u'エ'), + (0x32D4, 'M', u'オ'), + (0x32D5, 'M', u'カ'), + (0x32D6, 'M', u'キ'), + (0x32D7, 'M', u'ク'), + (0x32D8, 'M', u'ケ'), + (0x32D9, 'M', u'コ'), + (0x32DA, 'M', u'サ'), + (0x32DB, 'M', u'シ'), + (0x32DC, 'M', u'ス'), + (0x32DD, 'M', u'セ'), + (0x32DE, 'M', u'ソ'), + (0x32DF, 'M', u'タ'), + (0x32E0, 'M', u'チ'), + (0x32E1, 'M', u'ツ'), + (0x32E2, 'M', u'テ'), + (0x32E3, 'M', u'ト'), + (0x32E4, 'M', u'ナ'), + (0x32E5, 'M', u'ニ'), + (0x32E6, 'M', u'ヌ'), + (0x32E7, 'M', u'ネ'), + (0x32E8, 'M', u'ノ'), + (0x32E9, 'M', u'ハ'), + (0x32EA, 'M', u'ヒ'), + (0x32EB, 'M', u'フ'), + (0x32EC, 'M', u'ヘ'), + (0x32ED, 'M', u'ホ'), + (0x32EE, 'M', u'マ'), + (0x32EF, 'M', u'ミ'), + (0x32F0, 'M', u'ム'), + (0x32F1, 'M', u'メ'), + (0x32F2, 'M', u'モ'), + (0x32F3, 'M', u'ヤ'), + (0x32F4, 'M', u'ユ'), + (0x32F5, 'M', u'ヨ'), + (0x32F6, 'M', u'ラ'), + (0x32F7, 'M', u'リ'), + (0x32F8, 'M', u'ル'), + (0x32F9, 'M', u'レ'), + (0x32FA, 'M', u'ロ'), + (0x32FB, 'M', u'ワ'), + (0x32FC, 'M', u'ヰ'), + (0x32FD, 'M', u'ヱ'), + (0x32FE, 'M', u'ヲ'), + (0x32FF, 'X'), + (0x3300, 'M', u'アパート'), + (0x3301, 'M', u'アルファ'), + (0x3302, 'M', u'アンペア'), + (0x3303, 'M', u'アール'), + (0x3304, 'M', u'イニング'), + (0x3305, 'M', u'インチ'), + (0x3306, 'M', u'ウォン'), + (0x3307, 'M', u'エスクード'), + (0x3308, 'M', u'エーカー'), + (0x3309, 'M', u'オンス'), + (0x330A, 'M', u'オーム'), + (0x330B, 'M', u'カイリ'), + (0x330C, 'M', u'カラット'), + (0x330D, 'M', u'カロリー'), + (0x330E, 'M', u'ガロン'), + (0x330F, 'M', u'ガンマ'), + (0x3310, 'M', u'ギガ'), + (0x3311, 'M', u'ギニー'), + (0x3312, 'M', u'キュリー'), + (0x3313, 'M', u'ギルダー'), + (0x3314, 'M', u'キロ'), + (0x3315, 'M', u'キログラム'), + (0x3316, 'M', u'キロメートル'), + (0x3317, 'M', u'キロワット'), + (0x3318, 'M', u'グラム'), + (0x3319, 'M', u'グラムトン'), + (0x331A, 'M', u'クルゼイロ'), + (0x331B, 'M', u'クローネ'), + (0x331C, 'M', u'ケース'), + (0x331D, 'M', u'コルナ'), + (0x331E, 'M', u'コーポ'), + (0x331F, 'M', u'サイクル'), + (0x3320, 'M', u'サンチーム'), + (0x3321, 'M', u'シリング'), + (0x3322, 'M', u'センチ'), + (0x3323, 'M', u'セント'), + (0x3324, 'M', u'ダース'), + (0x3325, 'M', u'デシ'), + (0x3326, 'M', u'ドル'), + (0x3327, 'M', u'トン'), + (0x3328, 'M', u'ナノ'), + (0x3329, 'M', u'ノット'), + (0x332A, 'M', u'ハイツ'), + (0x332B, 'M', u'パーセント'), + ] + +def _seg_33(): + return [ + (0x332C, 'M', u'パーツ'), + (0x332D, 'M', u'バーレル'), + (0x332E, 'M', u'ピアストル'), + (0x332F, 'M', u'ピクル'), + (0x3330, 'M', u'ピコ'), + (0x3331, 'M', u'ビル'), + (0x3332, 'M', u'ファラッド'), + (0x3333, 'M', u'フィート'), + (0x3334, 'M', u'ブッシェル'), + (0x3335, 'M', u'フラン'), + (0x3336, 'M', u'ヘクタール'), + (0x3337, 'M', u'ペソ'), + (0x3338, 'M', u'ペニヒ'), + (0x3339, 'M', u'ヘルツ'), + (0x333A, 'M', u'ペンス'), + (0x333B, 'M', u'ページ'), + (0x333C, 'M', u'ベータ'), + (0x333D, 'M', u'ポイント'), + (0x333E, 'M', u'ボルト'), + (0x333F, 'M', u'ホン'), + (0x3340, 'M', u'ポンド'), + (0x3341, 'M', u'ホール'), + (0x3342, 'M', u'ホーン'), + (0x3343, 'M', u'マイクロ'), + (0x3344, 'M', u'マイル'), + (0x3345, 'M', u'マッハ'), + (0x3346, 'M', u'マルク'), + (0x3347, 'M', u'マンション'), + (0x3348, 'M', u'ミクロン'), + (0x3349, 'M', u'ミリ'), + (0x334A, 'M', u'ミリバール'), + (0x334B, 'M', u'メガ'), + (0x334C, 'M', u'メガトン'), + (0x334D, 'M', u'メートル'), + (0x334E, 'M', u'ヤード'), + (0x334F, 'M', u'ヤール'), + (0x3350, 'M', u'ユアン'), + (0x3351, 'M', u'リットル'), + (0x3352, 'M', u'リラ'), + (0x3353, 'M', u'ルピー'), + (0x3354, 'M', u'ルーブル'), + (0x3355, 'M', u'レム'), + (0x3356, 'M', u'レントゲン'), + (0x3357, 'M', u'ワット'), + (0x3358, 'M', u'0点'), + (0x3359, 'M', u'1点'), + (0x335A, 'M', u'2点'), + (0x335B, 'M', u'3点'), + (0x335C, 'M', u'4点'), + (0x335D, 'M', u'5点'), + (0x335E, 'M', u'6点'), + (0x335F, 'M', u'7点'), + (0x3360, 'M', u'8点'), + (0x3361, 'M', u'9点'), + (0x3362, 'M', u'10点'), + (0x3363, 'M', u'11点'), + (0x3364, 'M', u'12点'), + (0x3365, 'M', u'13点'), + (0x3366, 'M', u'14点'), + (0x3367, 'M', u'15点'), + (0x3368, 'M', u'16点'), + (0x3369, 'M', u'17点'), + (0x336A, 'M', u'18点'), + (0x336B, 'M', u'19点'), + (0x336C, 'M', u'20点'), + (0x336D, 'M', u'21点'), + (0x336E, 'M', u'22点'), + (0x336F, 'M', u'23点'), + (0x3370, 'M', u'24点'), + (0x3371, 'M', u'hpa'), + (0x3372, 'M', u'da'), + (0x3373, 'M', u'au'), + (0x3374, 'M', u'bar'), + (0x3375, 'M', u'ov'), + (0x3376, 'M', u'pc'), + (0x3377, 'M', u'dm'), + (0x3378, 'M', u'dm2'), + (0x3379, 'M', u'dm3'), + (0x337A, 'M', u'iu'), + (0x337B, 'M', u'平成'), + (0x337C, 'M', u'昭和'), + (0x337D, 'M', u'大正'), + (0x337E, 'M', u'明治'), + (0x337F, 'M', u'株式会社'), + (0x3380, 'M', u'pa'), + (0x3381, 'M', u'na'), + (0x3382, 'M', u'μa'), + (0x3383, 'M', u'ma'), + (0x3384, 'M', u'ka'), + (0x3385, 'M', u'kb'), + (0x3386, 'M', u'mb'), + (0x3387, 'M', u'gb'), + (0x3388, 'M', u'cal'), + (0x3389, 'M', u'kcal'), + (0x338A, 'M', u'pf'), + (0x338B, 'M', u'nf'), + (0x338C, 'M', u'μf'), + (0x338D, 'M', u'μg'), + (0x338E, 'M', u'mg'), + (0x338F, 'M', u'kg'), + ] + +def _seg_34(): + return [ + (0x3390, 'M', u'hz'), + (0x3391, 'M', u'khz'), + (0x3392, 'M', u'mhz'), + (0x3393, 'M', u'ghz'), + (0x3394, 'M', u'thz'), + (0x3395, 'M', u'μl'), + (0x3396, 'M', u'ml'), + (0x3397, 'M', u'dl'), + (0x3398, 'M', u'kl'), + (0x3399, 'M', u'fm'), + (0x339A, 'M', u'nm'), + (0x339B, 'M', u'μm'), + (0x339C, 'M', u'mm'), + (0x339D, 'M', u'cm'), + (0x339E, 'M', u'km'), + (0x339F, 'M', u'mm2'), + (0x33A0, 'M', u'cm2'), + (0x33A1, 'M', u'm2'), + (0x33A2, 'M', u'km2'), + (0x33A3, 'M', u'mm3'), + (0x33A4, 'M', u'cm3'), + (0x33A5, 'M', u'm3'), + (0x33A6, 'M', u'km3'), + (0x33A7, 'M', u'm∕s'), + (0x33A8, 'M', u'm∕s2'), + (0x33A9, 'M', u'pa'), + (0x33AA, 'M', u'kpa'), + (0x33AB, 'M', u'mpa'), + (0x33AC, 'M', u'gpa'), + (0x33AD, 'M', u'rad'), + (0x33AE, 'M', u'rad∕s'), + (0x33AF, 'M', u'rad∕s2'), + (0x33B0, 'M', u'ps'), + (0x33B1, 'M', u'ns'), + (0x33B2, 'M', u'μs'), + (0x33B3, 'M', u'ms'), + (0x33B4, 'M', u'pv'), + (0x33B5, 'M', u'nv'), + (0x33B6, 'M', u'μv'), + (0x33B7, 'M', u'mv'), + (0x33B8, 'M', u'kv'), + (0x33B9, 'M', u'mv'), + (0x33BA, 'M', u'pw'), + (0x33BB, 'M', u'nw'), + (0x33BC, 'M', u'μw'), + (0x33BD, 'M', u'mw'), + (0x33BE, 'M', u'kw'), + (0x33BF, 'M', u'mw'), + (0x33C0, 'M', u'kω'), + (0x33C1, 'M', u'mω'), + (0x33C2, 'X'), + (0x33C3, 'M', u'bq'), + (0x33C4, 'M', u'cc'), + (0x33C5, 'M', u'cd'), + (0x33C6, 'M', u'c∕kg'), + (0x33C7, 'X'), + (0x33C8, 'M', u'db'), + (0x33C9, 'M', u'gy'), + (0x33CA, 'M', u'ha'), + (0x33CB, 'M', u'hp'), + (0x33CC, 'M', u'in'), + (0x33CD, 'M', u'kk'), + (0x33CE, 'M', u'km'), + (0x33CF, 'M', u'kt'), + (0x33D0, 'M', u'lm'), + (0x33D1, 'M', u'ln'), + (0x33D2, 'M', u'log'), + (0x33D3, 'M', u'lx'), + (0x33D4, 'M', u'mb'), + (0x33D5, 'M', u'mil'), + (0x33D6, 'M', u'mol'), + (0x33D7, 'M', u'ph'), + (0x33D8, 'X'), + (0x33D9, 'M', u'ppm'), + (0x33DA, 'M', u'pr'), + (0x33DB, 'M', u'sr'), + (0x33DC, 'M', u'sv'), + (0x33DD, 'M', u'wb'), + (0x33DE, 'M', u'v∕m'), + (0x33DF, 'M', u'a∕m'), + (0x33E0, 'M', u'1日'), + (0x33E1, 'M', u'2日'), + (0x33E2, 'M', u'3日'), + (0x33E3, 'M', u'4日'), + (0x33E4, 'M', u'5日'), + (0x33E5, 'M', u'6日'), + (0x33E6, 'M', u'7日'), + (0x33E7, 'M', u'8日'), + (0x33E8, 'M', u'9日'), + (0x33E9, 'M', u'10日'), + (0x33EA, 'M', u'11日'), + (0x33EB, 'M', u'12日'), + (0x33EC, 'M', u'13日'), + (0x33ED, 'M', u'14日'), + (0x33EE, 'M', u'15日'), + (0x33EF, 'M', u'16日'), + (0x33F0, 'M', u'17日'), + (0x33F1, 'M', u'18日'), + (0x33F2, 'M', u'19日'), + (0x33F3, 'M', u'20日'), + ] + +def _seg_35(): + return [ + (0x33F4, 'M', u'21日'), + (0x33F5, 'M', u'22日'), + (0x33F6, 'M', u'23日'), + (0x33F7, 'M', u'24日'), + (0x33F8, 'M', u'25日'), + (0x33F9, 'M', u'26日'), + (0x33FA, 'M', u'27日'), + (0x33FB, 'M', u'28日'), + (0x33FC, 'M', u'29日'), + (0x33FD, 'M', u'30日'), + (0x33FE, 'M', u'31日'), + (0x33FF, 'M', u'gal'), + (0x3400, 'V'), + (0x4DB6, 'X'), + (0x4DC0, 'V'), + (0x9FF0, 'X'), + (0xA000, 'V'), + (0xA48D, 'X'), + (0xA490, 'V'), + (0xA4C7, 'X'), + (0xA4D0, 'V'), + (0xA62C, 'X'), + (0xA640, 'M', u'ꙁ'), + (0xA641, 'V'), + (0xA642, 'M', u'ꙃ'), + (0xA643, 'V'), + (0xA644, 'M', u'ꙅ'), + (0xA645, 'V'), + (0xA646, 'M', u'ꙇ'), + (0xA647, 'V'), + (0xA648, 'M', u'ꙉ'), + (0xA649, 'V'), + (0xA64A, 'M', u'ꙋ'), + (0xA64B, 'V'), + (0xA64C, 'M', u'ꙍ'), + (0xA64D, 'V'), + (0xA64E, 'M', u'ꙏ'), + (0xA64F, 'V'), + (0xA650, 'M', u'ꙑ'), + (0xA651, 'V'), + (0xA652, 'M', u'ꙓ'), + (0xA653, 'V'), + (0xA654, 'M', u'ꙕ'), + (0xA655, 'V'), + (0xA656, 'M', u'ꙗ'), + (0xA657, 'V'), + (0xA658, 'M', u'ꙙ'), + (0xA659, 'V'), + (0xA65A, 'M', u'ꙛ'), + (0xA65B, 'V'), + (0xA65C, 'M', u'ꙝ'), + (0xA65D, 'V'), + (0xA65E, 'M', u'ꙟ'), + (0xA65F, 'V'), + (0xA660, 'M', u'ꙡ'), + (0xA661, 'V'), + (0xA662, 'M', u'ꙣ'), + (0xA663, 'V'), + (0xA664, 'M', u'ꙥ'), + (0xA665, 'V'), + (0xA666, 'M', u'ꙧ'), + (0xA667, 'V'), + (0xA668, 'M', u'ꙩ'), + (0xA669, 'V'), + (0xA66A, 'M', u'ꙫ'), + (0xA66B, 'V'), + (0xA66C, 'M', u'ꙭ'), + (0xA66D, 'V'), + (0xA680, 'M', u'ꚁ'), + (0xA681, 'V'), + (0xA682, 'M', u'ꚃ'), + (0xA683, 'V'), + (0xA684, 'M', u'ꚅ'), + (0xA685, 'V'), + (0xA686, 'M', u'ꚇ'), + (0xA687, 'V'), + (0xA688, 'M', u'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', u'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', u'ꚍ'), + (0xA68D, 'V'), + (0xA68E, 'M', u'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', u'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', u'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', u'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', u'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', u'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', u'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', u'ъ'), + (0xA69D, 'M', u'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), + ] + +def _seg_36(): + return [ + (0xA700, 'V'), + (0xA722, 'M', u'ꜣ'), + (0xA723, 'V'), + (0xA724, 'M', u'ꜥ'), + (0xA725, 'V'), + (0xA726, 'M', u'ꜧ'), + (0xA727, 'V'), + (0xA728, 'M', u'ꜩ'), + (0xA729, 'V'), + (0xA72A, 'M', u'ꜫ'), + (0xA72B, 'V'), + (0xA72C, 'M', u'ꜭ'), + (0xA72D, 'V'), + (0xA72E, 'M', u'ꜯ'), + (0xA72F, 'V'), + (0xA732, 'M', u'ꜳ'), + (0xA733, 'V'), + (0xA734, 'M', u'ꜵ'), + (0xA735, 'V'), + (0xA736, 'M', u'ꜷ'), + (0xA737, 'V'), + (0xA738, 'M', u'ꜹ'), + (0xA739, 'V'), + (0xA73A, 'M', u'ꜻ'), + (0xA73B, 'V'), + (0xA73C, 'M', u'ꜽ'), + (0xA73D, 'V'), + (0xA73E, 'M', u'ꜿ'), + (0xA73F, 'V'), + (0xA740, 'M', u'ꝁ'), + (0xA741, 'V'), + (0xA742, 'M', u'ꝃ'), + (0xA743, 'V'), + (0xA744, 'M', u'ꝅ'), + (0xA745, 'V'), + (0xA746, 'M', u'ꝇ'), + (0xA747, 'V'), + (0xA748, 'M', u'ꝉ'), + (0xA749, 'V'), + (0xA74A, 'M', u'ꝋ'), + (0xA74B, 'V'), + (0xA74C, 'M', u'ꝍ'), + (0xA74D, 'V'), + (0xA74E, 'M', u'ꝏ'), + (0xA74F, 'V'), + (0xA750, 'M', u'ꝑ'), + (0xA751, 'V'), + (0xA752, 'M', u'ꝓ'), + (0xA753, 'V'), + (0xA754, 'M', u'ꝕ'), + (0xA755, 'V'), + (0xA756, 'M', u'ꝗ'), + (0xA757, 'V'), + (0xA758, 'M', u'ꝙ'), + (0xA759, 'V'), + (0xA75A, 'M', u'ꝛ'), + (0xA75B, 'V'), + (0xA75C, 'M', u'ꝝ'), + (0xA75D, 'V'), + (0xA75E, 'M', u'ꝟ'), + (0xA75F, 'V'), + (0xA760, 'M', u'ꝡ'), + (0xA761, 'V'), + (0xA762, 'M', u'ꝣ'), + (0xA763, 'V'), + (0xA764, 'M', u'ꝥ'), + (0xA765, 'V'), + (0xA766, 'M', u'ꝧ'), + (0xA767, 'V'), + (0xA768, 'M', u'ꝩ'), + (0xA769, 'V'), + (0xA76A, 'M', u'ꝫ'), + (0xA76B, 'V'), + (0xA76C, 'M', u'ꝭ'), + (0xA76D, 'V'), + (0xA76E, 'M', u'ꝯ'), + (0xA76F, 'V'), + (0xA770, 'M', u'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', u'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', u'ꝼ'), + (0xA77C, 'V'), + (0xA77D, 'M', u'ᵹ'), + (0xA77E, 'M', u'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', u'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', u'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', u'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', u'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', u'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', u'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', u'ꞑ'), + (0xA791, 'V'), + ] + +def _seg_37(): + return [ + (0xA792, 'M', u'ꞓ'), + (0xA793, 'V'), + (0xA796, 'M', u'ꞗ'), + (0xA797, 'V'), + (0xA798, 'M', u'ꞙ'), + (0xA799, 'V'), + (0xA79A, 'M', u'ꞛ'), + (0xA79B, 'V'), + (0xA79C, 'M', u'ꞝ'), + (0xA79D, 'V'), + (0xA79E, 'M', u'ꞟ'), + (0xA79F, 'V'), + (0xA7A0, 'M', u'ꞡ'), + (0xA7A1, 'V'), + (0xA7A2, 'M', u'ꞣ'), + (0xA7A3, 'V'), + (0xA7A4, 'M', u'ꞥ'), + (0xA7A5, 'V'), + (0xA7A6, 'M', u'ꞧ'), + (0xA7A7, 'V'), + (0xA7A8, 'M', u'ꞩ'), + (0xA7A9, 'V'), + (0xA7AA, 'M', u'ɦ'), + (0xA7AB, 'M', u'ɜ'), + (0xA7AC, 'M', u'ɡ'), + (0xA7AD, 'M', u'ɬ'), + (0xA7AE, 'M', u'ɪ'), + (0xA7AF, 'V'), + (0xA7B0, 'M', u'ʞ'), + (0xA7B1, 'M', u'ʇ'), + (0xA7B2, 'M', u'ʝ'), + (0xA7B3, 'M', u'ꭓ'), + (0xA7B4, 'M', u'ꞵ'), + (0xA7B5, 'V'), + (0xA7B6, 'M', u'ꞷ'), + (0xA7B7, 'V'), + (0xA7B8, 'X'), + (0xA7B9, 'V'), + (0xA7BA, 'X'), + (0xA7F7, 'V'), + (0xA7F8, 'M', u'ħ'), + (0xA7F9, 'M', u'œ'), + (0xA7FA, 'V'), + (0xA82C, 'X'), + (0xA830, 'V'), + (0xA83A, 'X'), + (0xA840, 'V'), + (0xA878, 'X'), + (0xA880, 'V'), + (0xA8C6, 'X'), + (0xA8CE, 'V'), + (0xA8DA, 'X'), + (0xA8E0, 'V'), + (0xA954, 'X'), + (0xA95F, 'V'), + (0xA97D, 'X'), + (0xA980, 'V'), + (0xA9CE, 'X'), + (0xA9CF, 'V'), + (0xA9DA, 'X'), + (0xA9DE, 'V'), + (0xA9FF, 'X'), + (0xAA00, 'V'), + (0xAA37, 'X'), + (0xAA40, 'V'), + (0xAA4E, 'X'), + (0xAA50, 'V'), + (0xAA5A, 'X'), + (0xAA5C, 'V'), + (0xAAC3, 'X'), + (0xAADB, 'V'), + (0xAAF7, 'X'), + (0xAB01, 'V'), + (0xAB07, 'X'), + (0xAB09, 'V'), + (0xAB0F, 'X'), + (0xAB11, 'V'), + (0xAB17, 'X'), + (0xAB20, 'V'), + (0xAB27, 'X'), + (0xAB28, 'V'), + (0xAB2F, 'X'), + (0xAB30, 'V'), + (0xAB5C, 'M', u'ꜧ'), + (0xAB5D, 'M', u'ꬷ'), + (0xAB5E, 'M', u'ɫ'), + (0xAB5F, 'M', u'ꭒ'), + (0xAB60, 'V'), + (0xAB66, 'X'), + (0xAB70, 'M', u'Ꭰ'), + (0xAB71, 'M', u'Ꭱ'), + (0xAB72, 'M', u'Ꭲ'), + (0xAB73, 'M', u'Ꭳ'), + (0xAB74, 'M', u'Ꭴ'), + (0xAB75, 'M', u'Ꭵ'), + (0xAB76, 'M', u'Ꭶ'), + (0xAB77, 'M', u'Ꭷ'), + (0xAB78, 'M', u'Ꭸ'), + (0xAB79, 'M', u'Ꭹ'), + (0xAB7A, 'M', u'Ꭺ'), + ] + +def _seg_38(): + return [ + (0xAB7B, 'M', u'Ꭻ'), + (0xAB7C, 'M', u'Ꭼ'), + (0xAB7D, 'M', u'Ꭽ'), + (0xAB7E, 'M', u'Ꭾ'), + (0xAB7F, 'M', u'Ꭿ'), + (0xAB80, 'M', u'Ꮀ'), + (0xAB81, 'M', u'Ꮁ'), + (0xAB82, 'M', u'Ꮂ'), + (0xAB83, 'M', u'Ꮃ'), + (0xAB84, 'M', u'Ꮄ'), + (0xAB85, 'M', u'Ꮅ'), + (0xAB86, 'M', u'Ꮆ'), + (0xAB87, 'M', u'Ꮇ'), + (0xAB88, 'M', u'Ꮈ'), + (0xAB89, 'M', u'Ꮉ'), + (0xAB8A, 'M', u'Ꮊ'), + (0xAB8B, 'M', u'Ꮋ'), + (0xAB8C, 'M', u'Ꮌ'), + (0xAB8D, 'M', u'Ꮍ'), + (0xAB8E, 'M', u'Ꮎ'), + (0xAB8F, 'M', u'Ꮏ'), + (0xAB90, 'M', u'Ꮐ'), + (0xAB91, 'M', u'Ꮑ'), + (0xAB92, 'M', u'Ꮒ'), + (0xAB93, 'M', u'Ꮓ'), + (0xAB94, 'M', u'Ꮔ'), + (0xAB95, 'M', u'Ꮕ'), + (0xAB96, 'M', u'Ꮖ'), + (0xAB97, 'M', u'Ꮗ'), + (0xAB98, 'M', u'Ꮘ'), + (0xAB99, 'M', u'Ꮙ'), + (0xAB9A, 'M', u'Ꮚ'), + (0xAB9B, 'M', u'Ꮛ'), + (0xAB9C, 'M', u'Ꮜ'), + (0xAB9D, 'M', u'Ꮝ'), + (0xAB9E, 'M', u'Ꮞ'), + (0xAB9F, 'M', u'Ꮟ'), + (0xABA0, 'M', u'Ꮠ'), + (0xABA1, 'M', u'Ꮡ'), + (0xABA2, 'M', u'Ꮢ'), + (0xABA3, 'M', u'Ꮣ'), + (0xABA4, 'M', u'Ꮤ'), + (0xABA5, 'M', u'Ꮥ'), + (0xABA6, 'M', u'Ꮦ'), + (0xABA7, 'M', u'Ꮧ'), + (0xABA8, 'M', u'Ꮨ'), + (0xABA9, 'M', u'Ꮩ'), + (0xABAA, 'M', u'Ꮪ'), + (0xABAB, 'M', u'Ꮫ'), + (0xABAC, 'M', u'Ꮬ'), + (0xABAD, 'M', u'Ꮭ'), + (0xABAE, 'M', u'Ꮮ'), + (0xABAF, 'M', u'Ꮯ'), + (0xABB0, 'M', u'Ꮰ'), + (0xABB1, 'M', u'Ꮱ'), + (0xABB2, 'M', u'Ꮲ'), + (0xABB3, 'M', u'Ꮳ'), + (0xABB4, 'M', u'Ꮴ'), + (0xABB5, 'M', u'Ꮵ'), + (0xABB6, 'M', u'Ꮶ'), + (0xABB7, 'M', u'Ꮷ'), + (0xABB8, 'M', u'Ꮸ'), + (0xABB9, 'M', u'Ꮹ'), + (0xABBA, 'M', u'Ꮺ'), + (0xABBB, 'M', u'Ꮻ'), + (0xABBC, 'M', u'Ꮼ'), + (0xABBD, 'M', u'Ꮽ'), + (0xABBE, 'M', u'Ꮾ'), + (0xABBF, 'M', u'Ꮿ'), + (0xABC0, 'V'), + (0xABEE, 'X'), + (0xABF0, 'V'), + (0xABFA, 'X'), + (0xAC00, 'V'), + (0xD7A4, 'X'), + (0xD7B0, 'V'), + (0xD7C7, 'X'), + (0xD7CB, 'V'), + (0xD7FC, 'X'), + (0xF900, 'M', u'豈'), + (0xF901, 'M', u'更'), + (0xF902, 'M', u'車'), + (0xF903, 'M', u'賈'), + (0xF904, 'M', u'滑'), + (0xF905, 'M', u'串'), + (0xF906, 'M', u'句'), + (0xF907, 'M', u'龜'), + (0xF909, 'M', u'契'), + (0xF90A, 'M', u'金'), + (0xF90B, 'M', u'喇'), + (0xF90C, 'M', u'奈'), + (0xF90D, 'M', u'懶'), + (0xF90E, 'M', u'癩'), + (0xF90F, 'M', u'羅'), + (0xF910, 'M', u'蘿'), + (0xF911, 'M', u'螺'), + (0xF912, 'M', u'裸'), + (0xF913, 'M', u'邏'), + (0xF914, 'M', u'樂'), + (0xF915, 'M', u'洛'), + ] + +def _seg_39(): + return [ + (0xF916, 'M', u'烙'), + (0xF917, 'M', u'珞'), + (0xF918, 'M', u'落'), + (0xF919, 'M', u'酪'), + (0xF91A, 'M', u'駱'), + (0xF91B, 'M', u'亂'), + (0xF91C, 'M', u'卵'), + (0xF91D, 'M', u'欄'), + (0xF91E, 'M', u'爛'), + (0xF91F, 'M', u'蘭'), + (0xF920, 'M', u'鸞'), + (0xF921, 'M', u'嵐'), + (0xF922, 'M', u'濫'), + (0xF923, 'M', u'藍'), + (0xF924, 'M', u'襤'), + (0xF925, 'M', u'拉'), + (0xF926, 'M', u'臘'), + (0xF927, 'M', u'蠟'), + (0xF928, 'M', u'廊'), + (0xF929, 'M', u'朗'), + (0xF92A, 'M', u'浪'), + (0xF92B, 'M', u'狼'), + (0xF92C, 'M', u'郎'), + (0xF92D, 'M', u'來'), + (0xF92E, 'M', u'冷'), + (0xF92F, 'M', u'勞'), + (0xF930, 'M', u'擄'), + (0xF931, 'M', u'櫓'), + (0xF932, 'M', u'爐'), + (0xF933, 'M', u'盧'), + (0xF934, 'M', u'老'), + (0xF935, 'M', u'蘆'), + (0xF936, 'M', u'虜'), + (0xF937, 'M', u'路'), + (0xF938, 'M', u'露'), + (0xF939, 'M', u'魯'), + (0xF93A, 'M', u'鷺'), + (0xF93B, 'M', u'碌'), + (0xF93C, 'M', u'祿'), + (0xF93D, 'M', u'綠'), + (0xF93E, 'M', u'菉'), + (0xF93F, 'M', u'錄'), + (0xF940, 'M', u'鹿'), + (0xF941, 'M', u'論'), + (0xF942, 'M', u'壟'), + (0xF943, 'M', u'弄'), + (0xF944, 'M', u'籠'), + (0xF945, 'M', u'聾'), + (0xF946, 'M', u'牢'), + (0xF947, 'M', u'磊'), + (0xF948, 'M', u'賂'), + (0xF949, 'M', u'雷'), + (0xF94A, 'M', u'壘'), + (0xF94B, 'M', u'屢'), + (0xF94C, 'M', u'樓'), + (0xF94D, 'M', u'淚'), + (0xF94E, 'M', u'漏'), + (0xF94F, 'M', u'累'), + (0xF950, 'M', u'縷'), + (0xF951, 'M', u'陋'), + (0xF952, 'M', u'勒'), + (0xF953, 'M', u'肋'), + (0xF954, 'M', u'凜'), + (0xF955, 'M', u'凌'), + (0xF956, 'M', u'稜'), + (0xF957, 'M', u'綾'), + (0xF958, 'M', u'菱'), + (0xF959, 'M', u'陵'), + (0xF95A, 'M', u'讀'), + (0xF95B, 'M', u'拏'), + (0xF95C, 'M', u'樂'), + (0xF95D, 'M', u'諾'), + (0xF95E, 'M', u'丹'), + (0xF95F, 'M', u'寧'), + (0xF960, 'M', u'怒'), + (0xF961, 'M', u'率'), + (0xF962, 'M', u'異'), + (0xF963, 'M', u'北'), + (0xF964, 'M', u'磻'), + (0xF965, 'M', u'便'), + (0xF966, 'M', u'復'), + (0xF967, 'M', u'不'), + (0xF968, 'M', u'泌'), + (0xF969, 'M', u'數'), + (0xF96A, 'M', u'索'), + (0xF96B, 'M', u'參'), + (0xF96C, 'M', u'塞'), + (0xF96D, 'M', u'省'), + (0xF96E, 'M', u'葉'), + (0xF96F, 'M', u'說'), + (0xF970, 'M', u'殺'), + (0xF971, 'M', u'辰'), + (0xF972, 'M', u'沈'), + (0xF973, 'M', u'拾'), + (0xF974, 'M', u'若'), + (0xF975, 'M', u'掠'), + (0xF976, 'M', u'略'), + (0xF977, 'M', u'亮'), + (0xF978, 'M', u'兩'), + (0xF979, 'M', u'凉'), + ] + +def _seg_40(): + return [ + (0xF97A, 'M', u'梁'), + (0xF97B, 'M', u'糧'), + (0xF97C, 'M', u'良'), + (0xF97D, 'M', u'諒'), + (0xF97E, 'M', u'量'), + (0xF97F, 'M', u'勵'), + (0xF980, 'M', u'呂'), + (0xF981, 'M', u'女'), + (0xF982, 'M', u'廬'), + (0xF983, 'M', u'旅'), + (0xF984, 'M', u'濾'), + (0xF985, 'M', u'礪'), + (0xF986, 'M', u'閭'), + (0xF987, 'M', u'驪'), + (0xF988, 'M', u'麗'), + (0xF989, 'M', u'黎'), + (0xF98A, 'M', u'力'), + (0xF98B, 'M', u'曆'), + (0xF98C, 'M', u'歷'), + (0xF98D, 'M', u'轢'), + (0xF98E, 'M', u'年'), + (0xF98F, 'M', u'憐'), + (0xF990, 'M', u'戀'), + (0xF991, 'M', u'撚'), + (0xF992, 'M', u'漣'), + (0xF993, 'M', u'煉'), + (0xF994, 'M', u'璉'), + (0xF995, 'M', u'秊'), + (0xF996, 'M', u'練'), + (0xF997, 'M', u'聯'), + (0xF998, 'M', u'輦'), + (0xF999, 'M', u'蓮'), + (0xF99A, 'M', u'連'), + (0xF99B, 'M', u'鍊'), + (0xF99C, 'M', u'列'), + (0xF99D, 'M', u'劣'), + (0xF99E, 'M', u'咽'), + (0xF99F, 'M', u'烈'), + (0xF9A0, 'M', u'裂'), + (0xF9A1, 'M', u'說'), + (0xF9A2, 'M', u'廉'), + (0xF9A3, 'M', u'念'), + (0xF9A4, 'M', u'捻'), + (0xF9A5, 'M', u'殮'), + (0xF9A6, 'M', u'簾'), + (0xF9A7, 'M', u'獵'), + (0xF9A8, 'M', u'令'), + (0xF9A9, 'M', u'囹'), + (0xF9AA, 'M', u'寧'), + (0xF9AB, 'M', u'嶺'), + (0xF9AC, 'M', u'怜'), + (0xF9AD, 'M', u'玲'), + (0xF9AE, 'M', u'瑩'), + (0xF9AF, 'M', u'羚'), + (0xF9B0, 'M', u'聆'), + (0xF9B1, 'M', u'鈴'), + (0xF9B2, 'M', u'零'), + (0xF9B3, 'M', u'靈'), + (0xF9B4, 'M', u'領'), + (0xF9B5, 'M', u'例'), + (0xF9B6, 'M', u'禮'), + (0xF9B7, 'M', u'醴'), + (0xF9B8, 'M', u'隸'), + (0xF9B9, 'M', u'惡'), + (0xF9BA, 'M', u'了'), + (0xF9BB, 'M', u'僚'), + (0xF9BC, 'M', u'寮'), + (0xF9BD, 'M', u'尿'), + (0xF9BE, 'M', u'料'), + (0xF9BF, 'M', u'樂'), + (0xF9C0, 'M', u'燎'), + (0xF9C1, 'M', u'療'), + (0xF9C2, 'M', u'蓼'), + (0xF9C3, 'M', u'遼'), + (0xF9C4, 'M', u'龍'), + (0xF9C5, 'M', u'暈'), + (0xF9C6, 'M', u'阮'), + (0xF9C7, 'M', u'劉'), + (0xF9C8, 'M', u'杻'), + (0xF9C9, 'M', u'柳'), + (0xF9CA, 'M', u'流'), + (0xF9CB, 'M', u'溜'), + (0xF9CC, 'M', u'琉'), + (0xF9CD, 'M', u'留'), + (0xF9CE, 'M', u'硫'), + (0xF9CF, 'M', u'紐'), + (0xF9D0, 'M', u'類'), + (0xF9D1, 'M', u'六'), + (0xF9D2, 'M', u'戮'), + (0xF9D3, 'M', u'陸'), + (0xF9D4, 'M', u'倫'), + (0xF9D5, 'M', u'崙'), + (0xF9D6, 'M', u'淪'), + (0xF9D7, 'M', u'輪'), + (0xF9D8, 'M', u'律'), + (0xF9D9, 'M', u'慄'), + (0xF9DA, 'M', u'栗'), + (0xF9DB, 'M', u'率'), + (0xF9DC, 'M', u'隆'), + (0xF9DD, 'M', u'利'), + ] + +def _seg_41(): + return [ + (0xF9DE, 'M', u'吏'), + (0xF9DF, 'M', u'履'), + (0xF9E0, 'M', u'易'), + (0xF9E1, 'M', u'李'), + (0xF9E2, 'M', u'梨'), + (0xF9E3, 'M', u'泥'), + (0xF9E4, 'M', u'理'), + (0xF9E5, 'M', u'痢'), + (0xF9E6, 'M', u'罹'), + (0xF9E7, 'M', u'裏'), + (0xF9E8, 'M', u'裡'), + (0xF9E9, 'M', u'里'), + (0xF9EA, 'M', u'離'), + (0xF9EB, 'M', u'匿'), + (0xF9EC, 'M', u'溺'), + (0xF9ED, 'M', u'吝'), + (0xF9EE, 'M', u'燐'), + (0xF9EF, 'M', u'璘'), + (0xF9F0, 'M', u'藺'), + (0xF9F1, 'M', u'隣'), + (0xF9F2, 'M', u'鱗'), + (0xF9F3, 'M', u'麟'), + (0xF9F4, 'M', u'林'), + (0xF9F5, 'M', u'淋'), + (0xF9F6, 'M', u'臨'), + (0xF9F7, 'M', u'立'), + (0xF9F8, 'M', u'笠'), + (0xF9F9, 'M', u'粒'), + (0xF9FA, 'M', u'狀'), + (0xF9FB, 'M', u'炙'), + (0xF9FC, 'M', u'識'), + (0xF9FD, 'M', u'什'), + (0xF9FE, 'M', u'茶'), + (0xF9FF, 'M', u'刺'), + (0xFA00, 'M', u'切'), + (0xFA01, 'M', u'度'), + (0xFA02, 'M', u'拓'), + (0xFA03, 'M', u'糖'), + (0xFA04, 'M', u'宅'), + (0xFA05, 'M', u'洞'), + (0xFA06, 'M', u'暴'), + (0xFA07, 'M', u'輻'), + (0xFA08, 'M', u'行'), + (0xFA09, 'M', u'降'), + (0xFA0A, 'M', u'見'), + (0xFA0B, 'M', u'廓'), + (0xFA0C, 'M', u'兀'), + (0xFA0D, 'M', u'嗀'), + (0xFA0E, 'V'), + (0xFA10, 'M', u'塚'), + (0xFA11, 'V'), + (0xFA12, 'M', u'晴'), + (0xFA13, 'V'), + (0xFA15, 'M', u'凞'), + (0xFA16, 'M', u'猪'), + (0xFA17, 'M', u'益'), + (0xFA18, 'M', u'礼'), + (0xFA19, 'M', u'神'), + (0xFA1A, 'M', u'祥'), + (0xFA1B, 'M', u'福'), + (0xFA1C, 'M', u'靖'), + (0xFA1D, 'M', u'精'), + (0xFA1E, 'M', u'羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', u'蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', u'諸'), + (0xFA23, 'V'), + (0xFA25, 'M', u'逸'), + (0xFA26, 'M', u'都'), + (0xFA27, 'V'), + (0xFA2A, 'M', u'飯'), + (0xFA2B, 'M', u'飼'), + (0xFA2C, 'M', u'館'), + (0xFA2D, 'M', u'鶴'), + (0xFA2E, 'M', u'郞'), + (0xFA2F, 'M', u'隷'), + (0xFA30, 'M', u'侮'), + (0xFA31, 'M', u'僧'), + (0xFA32, 'M', u'免'), + (0xFA33, 'M', u'勉'), + (0xFA34, 'M', u'勤'), + (0xFA35, 'M', u'卑'), + (0xFA36, 'M', u'喝'), + (0xFA37, 'M', u'嘆'), + (0xFA38, 'M', u'器'), + (0xFA39, 'M', u'塀'), + (0xFA3A, 'M', u'墨'), + (0xFA3B, 'M', u'層'), + (0xFA3C, 'M', u'屮'), + (0xFA3D, 'M', u'悔'), + (0xFA3E, 'M', u'慨'), + (0xFA3F, 'M', u'憎'), + (0xFA40, 'M', u'懲'), + (0xFA41, 'M', u'敏'), + (0xFA42, 'M', u'既'), + (0xFA43, 'M', u'暑'), + (0xFA44, 'M', u'梅'), + (0xFA45, 'M', u'海'), + (0xFA46, 'M', u'渚'), + ] + +def _seg_42(): + return [ + (0xFA47, 'M', u'漢'), + (0xFA48, 'M', u'煮'), + (0xFA49, 'M', u'爫'), + (0xFA4A, 'M', u'琢'), + (0xFA4B, 'M', u'碑'), + (0xFA4C, 'M', u'社'), + (0xFA4D, 'M', u'祉'), + (0xFA4E, 'M', u'祈'), + (0xFA4F, 'M', u'祐'), + (0xFA50, 'M', u'祖'), + (0xFA51, 'M', u'祝'), + (0xFA52, 'M', u'禍'), + (0xFA53, 'M', u'禎'), + (0xFA54, 'M', u'穀'), + (0xFA55, 'M', u'突'), + (0xFA56, 'M', u'節'), + (0xFA57, 'M', u'練'), + (0xFA58, 'M', u'縉'), + (0xFA59, 'M', u'繁'), + (0xFA5A, 'M', u'署'), + (0xFA5B, 'M', u'者'), + (0xFA5C, 'M', u'臭'), + (0xFA5D, 'M', u'艹'), + (0xFA5F, 'M', u'著'), + (0xFA60, 'M', u'褐'), + (0xFA61, 'M', u'視'), + (0xFA62, 'M', u'謁'), + (0xFA63, 'M', u'謹'), + (0xFA64, 'M', u'賓'), + (0xFA65, 'M', u'贈'), + (0xFA66, 'M', u'辶'), + (0xFA67, 'M', u'逸'), + (0xFA68, 'M', u'難'), + (0xFA69, 'M', u'響'), + (0xFA6A, 'M', u'頻'), + (0xFA6B, 'M', u'恵'), + (0xFA6C, 'M', u'𤋮'), + (0xFA6D, 'M', u'舘'), + (0xFA6E, 'X'), + (0xFA70, 'M', u'並'), + (0xFA71, 'M', u'况'), + (0xFA72, 'M', u'全'), + (0xFA73, 'M', u'侀'), + (0xFA74, 'M', u'充'), + (0xFA75, 'M', u'冀'), + (0xFA76, 'M', u'勇'), + (0xFA77, 'M', u'勺'), + (0xFA78, 'M', u'喝'), + (0xFA79, 'M', u'啕'), + (0xFA7A, 'M', u'喙'), + (0xFA7B, 'M', u'嗢'), + (0xFA7C, 'M', u'塚'), + (0xFA7D, 'M', u'墳'), + (0xFA7E, 'M', u'奄'), + (0xFA7F, 'M', u'奔'), + (0xFA80, 'M', u'婢'), + (0xFA81, 'M', u'嬨'), + (0xFA82, 'M', u'廒'), + (0xFA83, 'M', u'廙'), + (0xFA84, 'M', u'彩'), + (0xFA85, 'M', u'徭'), + (0xFA86, 'M', u'惘'), + (0xFA87, 'M', u'慎'), + (0xFA88, 'M', u'愈'), + (0xFA89, 'M', u'憎'), + (0xFA8A, 'M', u'慠'), + (0xFA8B, 'M', u'懲'), + (0xFA8C, 'M', u'戴'), + (0xFA8D, 'M', u'揄'), + (0xFA8E, 'M', u'搜'), + (0xFA8F, 'M', u'摒'), + (0xFA90, 'M', u'敖'), + (0xFA91, 'M', u'晴'), + (0xFA92, 'M', u'朗'), + (0xFA93, 'M', u'望'), + (0xFA94, 'M', u'杖'), + (0xFA95, 'M', u'歹'), + (0xFA96, 'M', u'殺'), + (0xFA97, 'M', u'流'), + (0xFA98, 'M', u'滛'), + (0xFA99, 'M', u'滋'), + (0xFA9A, 'M', u'漢'), + (0xFA9B, 'M', u'瀞'), + (0xFA9C, 'M', u'煮'), + (0xFA9D, 'M', u'瞧'), + (0xFA9E, 'M', u'爵'), + (0xFA9F, 'M', u'犯'), + (0xFAA0, 'M', u'猪'), + (0xFAA1, 'M', u'瑱'), + (0xFAA2, 'M', u'甆'), + (0xFAA3, 'M', u'画'), + (0xFAA4, 'M', u'瘝'), + (0xFAA5, 'M', u'瘟'), + (0xFAA6, 'M', u'益'), + (0xFAA7, 'M', u'盛'), + (0xFAA8, 'M', u'直'), + (0xFAA9, 'M', u'睊'), + (0xFAAA, 'M', u'着'), + (0xFAAB, 'M', u'磌'), + (0xFAAC, 'M', u'窱'), + ] + +def _seg_43(): + return [ + (0xFAAD, 'M', u'節'), + (0xFAAE, 'M', u'类'), + (0xFAAF, 'M', u'絛'), + (0xFAB0, 'M', u'練'), + (0xFAB1, 'M', u'缾'), + (0xFAB2, 'M', u'者'), + (0xFAB3, 'M', u'荒'), + (0xFAB4, 'M', u'華'), + (0xFAB5, 'M', u'蝹'), + (0xFAB6, 'M', u'襁'), + (0xFAB7, 'M', u'覆'), + (0xFAB8, 'M', u'視'), + (0xFAB9, 'M', u'調'), + (0xFABA, 'M', u'諸'), + (0xFABB, 'M', u'請'), + (0xFABC, 'M', u'謁'), + (0xFABD, 'M', u'諾'), + (0xFABE, 'M', u'諭'), + (0xFABF, 'M', u'謹'), + (0xFAC0, 'M', u'變'), + (0xFAC1, 'M', u'贈'), + (0xFAC2, 'M', u'輸'), + (0xFAC3, 'M', u'遲'), + (0xFAC4, 'M', u'醙'), + (0xFAC5, 'M', u'鉶'), + (0xFAC6, 'M', u'陼'), + (0xFAC7, 'M', u'難'), + (0xFAC8, 'M', u'靖'), + (0xFAC9, 'M', u'韛'), + (0xFACA, 'M', u'響'), + (0xFACB, 'M', u'頋'), + (0xFACC, 'M', u'頻'), + (0xFACD, 'M', u'鬒'), + (0xFACE, 'M', u'龜'), + (0xFACF, 'M', u'𢡊'), + (0xFAD0, 'M', u'𢡄'), + (0xFAD1, 'M', u'𣏕'), + (0xFAD2, 'M', u'㮝'), + (0xFAD3, 'M', u'䀘'), + (0xFAD4, 'M', u'䀹'), + (0xFAD5, 'M', u'𥉉'), + (0xFAD6, 'M', u'𥳐'), + (0xFAD7, 'M', u'𧻓'), + (0xFAD8, 'M', u'齃'), + (0xFAD9, 'M', u'龎'), + (0xFADA, 'X'), + (0xFB00, 'M', u'ff'), + (0xFB01, 'M', u'fi'), + (0xFB02, 'M', u'fl'), + (0xFB03, 'M', u'ffi'), + (0xFB04, 'M', u'ffl'), + (0xFB05, 'M', u'st'), + (0xFB07, 'X'), + (0xFB13, 'M', u'մն'), + (0xFB14, 'M', u'մե'), + (0xFB15, 'M', u'մի'), + (0xFB16, 'M', u'վն'), + (0xFB17, 'M', u'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', u'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', u'ײַ'), + (0xFB20, 'M', u'ע'), + (0xFB21, 'M', u'א'), + (0xFB22, 'M', u'ד'), + (0xFB23, 'M', u'ה'), + (0xFB24, 'M', u'כ'), + (0xFB25, 'M', u'ל'), + (0xFB26, 'M', u'ם'), + (0xFB27, 'M', u'ר'), + (0xFB28, 'M', u'ת'), + (0xFB29, '3', u'+'), + (0xFB2A, 'M', u'שׁ'), + (0xFB2B, 'M', u'שׂ'), + (0xFB2C, 'M', u'שּׁ'), + (0xFB2D, 'M', u'שּׂ'), + (0xFB2E, 'M', u'אַ'), + (0xFB2F, 'M', u'אָ'), + (0xFB30, 'M', u'אּ'), + (0xFB31, 'M', u'בּ'), + (0xFB32, 'M', u'גּ'), + (0xFB33, 'M', u'דּ'), + (0xFB34, 'M', u'הּ'), + (0xFB35, 'M', u'וּ'), + (0xFB36, 'M', u'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', u'טּ'), + (0xFB39, 'M', u'יּ'), + (0xFB3A, 'M', u'ךּ'), + (0xFB3B, 'M', u'כּ'), + (0xFB3C, 'M', u'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', u'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', u'נּ'), + (0xFB41, 'M', u'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', u'ףּ'), + (0xFB44, 'M', u'פּ'), + (0xFB45, 'X'), + ] + +def _seg_44(): + return [ + (0xFB46, 'M', u'צּ'), + (0xFB47, 'M', u'קּ'), + (0xFB48, 'M', u'רּ'), + (0xFB49, 'M', u'שּ'), + (0xFB4A, 'M', u'תּ'), + (0xFB4B, 'M', u'וֹ'), + (0xFB4C, 'M', u'בֿ'), + (0xFB4D, 'M', u'כֿ'), + (0xFB4E, 'M', u'פֿ'), + (0xFB4F, 'M', u'אל'), + (0xFB50, 'M', u'ٱ'), + (0xFB52, 'M', u'ٻ'), + (0xFB56, 'M', u'پ'), + (0xFB5A, 'M', u'ڀ'), + (0xFB5E, 'M', u'ٺ'), + (0xFB62, 'M', u'ٿ'), + (0xFB66, 'M', u'ٹ'), + (0xFB6A, 'M', u'ڤ'), + (0xFB6E, 'M', u'ڦ'), + (0xFB72, 'M', u'ڄ'), + (0xFB76, 'M', u'ڃ'), + (0xFB7A, 'M', u'چ'), + (0xFB7E, 'M', u'ڇ'), + (0xFB82, 'M', u'ڍ'), + (0xFB84, 'M', u'ڌ'), + (0xFB86, 'M', u'ڎ'), + (0xFB88, 'M', u'ڈ'), + (0xFB8A, 'M', u'ژ'), + (0xFB8C, 'M', u'ڑ'), + (0xFB8E, 'M', u'ک'), + (0xFB92, 'M', u'گ'), + (0xFB96, 'M', u'ڳ'), + (0xFB9A, 'M', u'ڱ'), + (0xFB9E, 'M', u'ں'), + (0xFBA0, 'M', u'ڻ'), + (0xFBA4, 'M', u'ۀ'), + (0xFBA6, 'M', u'ہ'), + (0xFBAA, 'M', u'ھ'), + (0xFBAE, 'M', u'ے'), + (0xFBB0, 'M', u'ۓ'), + (0xFBB2, 'V'), + (0xFBC2, 'X'), + (0xFBD3, 'M', u'ڭ'), + (0xFBD7, 'M', u'ۇ'), + (0xFBD9, 'M', u'ۆ'), + (0xFBDB, 'M', u'ۈ'), + (0xFBDD, 'M', u'ۇٴ'), + (0xFBDE, 'M', u'ۋ'), + (0xFBE0, 'M', u'ۅ'), + (0xFBE2, 'M', u'ۉ'), + (0xFBE4, 'M', u'ې'), + (0xFBE8, 'M', u'ى'), + (0xFBEA, 'M', u'ئا'), + (0xFBEC, 'M', u'ئە'), + (0xFBEE, 'M', u'ئو'), + (0xFBF0, 'M', u'ئۇ'), + (0xFBF2, 'M', u'ئۆ'), + (0xFBF4, 'M', u'ئۈ'), + (0xFBF6, 'M', u'ئې'), + (0xFBF9, 'M', u'ئى'), + (0xFBFC, 'M', u'ی'), + (0xFC00, 'M', u'ئج'), + (0xFC01, 'M', u'ئح'), + (0xFC02, 'M', u'ئم'), + (0xFC03, 'M', u'ئى'), + (0xFC04, 'M', u'ئي'), + (0xFC05, 'M', u'بج'), + (0xFC06, 'M', u'بح'), + (0xFC07, 'M', u'بخ'), + (0xFC08, 'M', u'بم'), + (0xFC09, 'M', u'بى'), + (0xFC0A, 'M', u'بي'), + (0xFC0B, 'M', u'تج'), + (0xFC0C, 'M', u'تح'), + (0xFC0D, 'M', u'تخ'), + (0xFC0E, 'M', u'تم'), + (0xFC0F, 'M', u'تى'), + (0xFC10, 'M', u'تي'), + (0xFC11, 'M', u'ثج'), + (0xFC12, 'M', u'ثم'), + (0xFC13, 'M', u'ثى'), + (0xFC14, 'M', u'ثي'), + (0xFC15, 'M', u'جح'), + (0xFC16, 'M', u'جم'), + (0xFC17, 'M', u'حج'), + (0xFC18, 'M', u'حم'), + (0xFC19, 'M', u'خج'), + (0xFC1A, 'M', u'خح'), + (0xFC1B, 'M', u'خم'), + (0xFC1C, 'M', u'سج'), + (0xFC1D, 'M', u'سح'), + (0xFC1E, 'M', u'سخ'), + (0xFC1F, 'M', u'سم'), + (0xFC20, 'M', u'صح'), + (0xFC21, 'M', u'صم'), + (0xFC22, 'M', u'ضج'), + (0xFC23, 'M', u'ضح'), + (0xFC24, 'M', u'ضخ'), + (0xFC25, 'M', u'ضم'), + (0xFC26, 'M', u'طح'), + ] + +def _seg_45(): + return [ + (0xFC27, 'M', u'طم'), + (0xFC28, 'M', u'ظم'), + (0xFC29, 'M', u'عج'), + (0xFC2A, 'M', u'عم'), + (0xFC2B, 'M', u'غج'), + (0xFC2C, 'M', u'غم'), + (0xFC2D, 'M', u'فج'), + (0xFC2E, 'M', u'فح'), + (0xFC2F, 'M', u'فخ'), + (0xFC30, 'M', u'فم'), + (0xFC31, 'M', u'فى'), + (0xFC32, 'M', u'في'), + (0xFC33, 'M', u'قح'), + (0xFC34, 'M', u'قم'), + (0xFC35, 'M', u'قى'), + (0xFC36, 'M', u'قي'), + (0xFC37, 'M', u'كا'), + (0xFC38, 'M', u'كج'), + (0xFC39, 'M', u'كح'), + (0xFC3A, 'M', u'كخ'), + (0xFC3B, 'M', u'كل'), + (0xFC3C, 'M', u'كم'), + (0xFC3D, 'M', u'كى'), + (0xFC3E, 'M', u'كي'), + (0xFC3F, 'M', u'لج'), + (0xFC40, 'M', u'لح'), + (0xFC41, 'M', u'لخ'), + (0xFC42, 'M', u'لم'), + (0xFC43, 'M', u'لى'), + (0xFC44, 'M', u'لي'), + (0xFC45, 'M', u'مج'), + (0xFC46, 'M', u'مح'), + (0xFC47, 'M', u'مخ'), + (0xFC48, 'M', u'مم'), + (0xFC49, 'M', u'مى'), + (0xFC4A, 'M', u'مي'), + (0xFC4B, 'M', u'نج'), + (0xFC4C, 'M', u'نح'), + (0xFC4D, 'M', u'نخ'), + (0xFC4E, 'M', u'نم'), + (0xFC4F, 'M', u'نى'), + (0xFC50, 'M', u'ني'), + (0xFC51, 'M', u'هج'), + (0xFC52, 'M', u'هم'), + (0xFC53, 'M', u'هى'), + (0xFC54, 'M', u'هي'), + (0xFC55, 'M', u'يج'), + (0xFC56, 'M', u'يح'), + (0xFC57, 'M', u'يخ'), + (0xFC58, 'M', u'يم'), + (0xFC59, 'M', u'يى'), + (0xFC5A, 'M', u'يي'), + (0xFC5B, 'M', u'ذٰ'), + (0xFC5C, 'M', u'رٰ'), + (0xFC5D, 'M', u'ىٰ'), + (0xFC5E, '3', u' ٌّ'), + (0xFC5F, '3', u' ٍّ'), + (0xFC60, '3', u' َّ'), + (0xFC61, '3', u' ُّ'), + (0xFC62, '3', u' ِّ'), + (0xFC63, '3', u' ّٰ'), + (0xFC64, 'M', u'ئر'), + (0xFC65, 'M', u'ئز'), + (0xFC66, 'M', u'ئم'), + (0xFC67, 'M', u'ئن'), + (0xFC68, 'M', u'ئى'), + (0xFC69, 'M', u'ئي'), + (0xFC6A, 'M', u'بر'), + (0xFC6B, 'M', u'بز'), + (0xFC6C, 'M', u'بم'), + (0xFC6D, 'M', u'بن'), + (0xFC6E, 'M', u'بى'), + (0xFC6F, 'M', u'بي'), + (0xFC70, 'M', u'تر'), + (0xFC71, 'M', u'تز'), + (0xFC72, 'M', u'تم'), + (0xFC73, 'M', u'تن'), + (0xFC74, 'M', u'تى'), + (0xFC75, 'M', u'تي'), + (0xFC76, 'M', u'ثر'), + (0xFC77, 'M', u'ثز'), + (0xFC78, 'M', u'ثم'), + (0xFC79, 'M', u'ثن'), + (0xFC7A, 'M', u'ثى'), + (0xFC7B, 'M', u'ثي'), + (0xFC7C, 'M', u'فى'), + (0xFC7D, 'M', u'في'), + (0xFC7E, 'M', u'قى'), + (0xFC7F, 'M', u'قي'), + (0xFC80, 'M', u'كا'), + (0xFC81, 'M', u'كل'), + (0xFC82, 'M', u'كم'), + (0xFC83, 'M', u'كى'), + (0xFC84, 'M', u'كي'), + (0xFC85, 'M', u'لم'), + (0xFC86, 'M', u'لى'), + (0xFC87, 'M', u'لي'), + (0xFC88, 'M', u'ما'), + (0xFC89, 'M', u'مم'), + (0xFC8A, 'M', u'نر'), + ] + +def _seg_46(): + return [ + (0xFC8B, 'M', u'نز'), + (0xFC8C, 'M', u'نم'), + (0xFC8D, 'M', u'نن'), + (0xFC8E, 'M', u'نى'), + (0xFC8F, 'M', u'ني'), + (0xFC90, 'M', u'ىٰ'), + (0xFC91, 'M', u'ير'), + (0xFC92, 'M', u'يز'), + (0xFC93, 'M', u'يم'), + (0xFC94, 'M', u'ين'), + (0xFC95, 'M', u'يى'), + (0xFC96, 'M', u'يي'), + (0xFC97, 'M', u'ئج'), + (0xFC98, 'M', u'ئح'), + (0xFC99, 'M', u'ئخ'), + (0xFC9A, 'M', u'ئم'), + (0xFC9B, 'M', u'ئه'), + (0xFC9C, 'M', u'بج'), + (0xFC9D, 'M', u'بح'), + (0xFC9E, 'M', u'بخ'), + (0xFC9F, 'M', u'بم'), + (0xFCA0, 'M', u'به'), + (0xFCA1, 'M', u'تج'), + (0xFCA2, 'M', u'تح'), + (0xFCA3, 'M', u'تخ'), + (0xFCA4, 'M', u'تم'), + (0xFCA5, 'M', u'ته'), + (0xFCA6, 'M', u'ثم'), + (0xFCA7, 'M', u'جح'), + (0xFCA8, 'M', u'جم'), + (0xFCA9, 'M', u'حج'), + (0xFCAA, 'M', u'حم'), + (0xFCAB, 'M', u'خج'), + (0xFCAC, 'M', u'خم'), + (0xFCAD, 'M', u'سج'), + (0xFCAE, 'M', u'سح'), + (0xFCAF, 'M', u'سخ'), + (0xFCB0, 'M', u'سم'), + (0xFCB1, 'M', u'صح'), + (0xFCB2, 'M', u'صخ'), + (0xFCB3, 'M', u'صم'), + (0xFCB4, 'M', u'ضج'), + (0xFCB5, 'M', u'ضح'), + (0xFCB6, 'M', u'ضخ'), + (0xFCB7, 'M', u'ضم'), + (0xFCB8, 'M', u'طح'), + (0xFCB9, 'M', u'ظم'), + (0xFCBA, 'M', u'عج'), + (0xFCBB, 'M', u'عم'), + (0xFCBC, 'M', u'غج'), + (0xFCBD, 'M', u'غم'), + (0xFCBE, 'M', u'فج'), + (0xFCBF, 'M', u'فح'), + (0xFCC0, 'M', u'فخ'), + (0xFCC1, 'M', u'فم'), + (0xFCC2, 'M', u'قح'), + (0xFCC3, 'M', u'قم'), + (0xFCC4, 'M', u'كج'), + (0xFCC5, 'M', u'كح'), + (0xFCC6, 'M', u'كخ'), + (0xFCC7, 'M', u'كل'), + (0xFCC8, 'M', u'كم'), + (0xFCC9, 'M', u'لج'), + (0xFCCA, 'M', u'لح'), + (0xFCCB, 'M', u'لخ'), + (0xFCCC, 'M', u'لم'), + (0xFCCD, 'M', u'له'), + (0xFCCE, 'M', u'مج'), + (0xFCCF, 'M', u'مح'), + (0xFCD0, 'M', u'مخ'), + (0xFCD1, 'M', u'مم'), + (0xFCD2, 'M', u'نج'), + (0xFCD3, 'M', u'نح'), + (0xFCD4, 'M', u'نخ'), + (0xFCD5, 'M', u'نم'), + (0xFCD6, 'M', u'نه'), + (0xFCD7, 'M', u'هج'), + (0xFCD8, 'M', u'هم'), + (0xFCD9, 'M', u'هٰ'), + (0xFCDA, 'M', u'يج'), + (0xFCDB, 'M', u'يح'), + (0xFCDC, 'M', u'يخ'), + (0xFCDD, 'M', u'يم'), + (0xFCDE, 'M', u'يه'), + (0xFCDF, 'M', u'ئم'), + (0xFCE0, 'M', u'ئه'), + (0xFCE1, 'M', u'بم'), + (0xFCE2, 'M', u'به'), + (0xFCE3, 'M', u'تم'), + (0xFCE4, 'M', u'ته'), + (0xFCE5, 'M', u'ثم'), + (0xFCE6, 'M', u'ثه'), + (0xFCE7, 'M', u'سم'), + (0xFCE8, 'M', u'سه'), + (0xFCE9, 'M', u'شم'), + (0xFCEA, 'M', u'شه'), + (0xFCEB, 'M', u'كل'), + (0xFCEC, 'M', u'كم'), + (0xFCED, 'M', u'لم'), + (0xFCEE, 'M', u'نم'), + ] + +def _seg_47(): + return [ + (0xFCEF, 'M', u'نه'), + (0xFCF0, 'M', u'يم'), + (0xFCF1, 'M', u'يه'), + (0xFCF2, 'M', u'ـَّ'), + (0xFCF3, 'M', u'ـُّ'), + (0xFCF4, 'M', u'ـِّ'), + (0xFCF5, 'M', u'طى'), + (0xFCF6, 'M', u'طي'), + (0xFCF7, 'M', u'عى'), + (0xFCF8, 'M', u'عي'), + (0xFCF9, 'M', u'غى'), + (0xFCFA, 'M', u'غي'), + (0xFCFB, 'M', u'سى'), + (0xFCFC, 'M', u'سي'), + (0xFCFD, 'M', u'شى'), + (0xFCFE, 'M', u'شي'), + (0xFCFF, 'M', u'حى'), + (0xFD00, 'M', u'حي'), + (0xFD01, 'M', u'جى'), + (0xFD02, 'M', u'جي'), + (0xFD03, 'M', u'خى'), + (0xFD04, 'M', u'خي'), + (0xFD05, 'M', u'صى'), + (0xFD06, 'M', u'صي'), + (0xFD07, 'M', u'ضى'), + (0xFD08, 'M', u'ضي'), + (0xFD09, 'M', u'شج'), + (0xFD0A, 'M', u'شح'), + (0xFD0B, 'M', u'شخ'), + (0xFD0C, 'M', u'شم'), + (0xFD0D, 'M', u'شر'), + (0xFD0E, 'M', u'سر'), + (0xFD0F, 'M', u'صر'), + (0xFD10, 'M', u'ضر'), + (0xFD11, 'M', u'طى'), + (0xFD12, 'M', u'طي'), + (0xFD13, 'M', u'عى'), + (0xFD14, 'M', u'عي'), + (0xFD15, 'M', u'غى'), + (0xFD16, 'M', u'غي'), + (0xFD17, 'M', u'سى'), + (0xFD18, 'M', u'سي'), + (0xFD19, 'M', u'شى'), + (0xFD1A, 'M', u'شي'), + (0xFD1B, 'M', u'حى'), + (0xFD1C, 'M', u'حي'), + (0xFD1D, 'M', u'جى'), + (0xFD1E, 'M', u'جي'), + (0xFD1F, 'M', u'خى'), + (0xFD20, 'M', u'خي'), + (0xFD21, 'M', u'صى'), + (0xFD22, 'M', u'صي'), + (0xFD23, 'M', u'ضى'), + (0xFD24, 'M', u'ضي'), + (0xFD25, 'M', u'شج'), + (0xFD26, 'M', u'شح'), + (0xFD27, 'M', u'شخ'), + (0xFD28, 'M', u'شم'), + (0xFD29, 'M', u'شر'), + (0xFD2A, 'M', u'سر'), + (0xFD2B, 'M', u'صر'), + (0xFD2C, 'M', u'ضر'), + (0xFD2D, 'M', u'شج'), + (0xFD2E, 'M', u'شح'), + (0xFD2F, 'M', u'شخ'), + (0xFD30, 'M', u'شم'), + (0xFD31, 'M', u'سه'), + (0xFD32, 'M', u'شه'), + (0xFD33, 'M', u'طم'), + (0xFD34, 'M', u'سج'), + (0xFD35, 'M', u'سح'), + (0xFD36, 'M', u'سخ'), + (0xFD37, 'M', u'شج'), + (0xFD38, 'M', u'شح'), + (0xFD39, 'M', u'شخ'), + (0xFD3A, 'M', u'طم'), + (0xFD3B, 'M', u'ظم'), + (0xFD3C, 'M', u'اً'), + (0xFD3E, 'V'), + (0xFD40, 'X'), + (0xFD50, 'M', u'تجم'), + (0xFD51, 'M', u'تحج'), + (0xFD53, 'M', u'تحم'), + (0xFD54, 'M', u'تخم'), + (0xFD55, 'M', u'تمج'), + (0xFD56, 'M', u'تمح'), + (0xFD57, 'M', u'تمخ'), + (0xFD58, 'M', u'جمح'), + (0xFD5A, 'M', u'حمي'), + (0xFD5B, 'M', u'حمى'), + (0xFD5C, 'M', u'سحج'), + (0xFD5D, 'M', u'سجح'), + (0xFD5E, 'M', u'سجى'), + (0xFD5F, 'M', u'سمح'), + (0xFD61, 'M', u'سمج'), + (0xFD62, 'M', u'سمم'), + (0xFD64, 'M', u'صحح'), + (0xFD66, 'M', u'صمم'), + (0xFD67, 'M', u'شحم'), + (0xFD69, 'M', u'شجي'), + ] + +def _seg_48(): + return [ + (0xFD6A, 'M', u'شمخ'), + (0xFD6C, 'M', u'شمم'), + (0xFD6E, 'M', u'ضحى'), + (0xFD6F, 'M', u'ضخم'), + (0xFD71, 'M', u'طمح'), + (0xFD73, 'M', u'طمم'), + (0xFD74, 'M', u'طمي'), + (0xFD75, 'M', u'عجم'), + (0xFD76, 'M', u'عمم'), + (0xFD78, 'M', u'عمى'), + (0xFD79, 'M', u'غمم'), + (0xFD7A, 'M', u'غمي'), + (0xFD7B, 'M', u'غمى'), + (0xFD7C, 'M', u'فخم'), + (0xFD7E, 'M', u'قمح'), + (0xFD7F, 'M', u'قمم'), + (0xFD80, 'M', u'لحم'), + (0xFD81, 'M', u'لحي'), + (0xFD82, 'M', u'لحى'), + (0xFD83, 'M', u'لجج'), + (0xFD85, 'M', u'لخم'), + (0xFD87, 'M', u'لمح'), + (0xFD89, 'M', u'محج'), + (0xFD8A, 'M', u'محم'), + (0xFD8B, 'M', u'محي'), + (0xFD8C, 'M', u'مجح'), + (0xFD8D, 'M', u'مجم'), + (0xFD8E, 'M', u'مخج'), + (0xFD8F, 'M', u'مخم'), + (0xFD90, 'X'), + (0xFD92, 'M', u'مجخ'), + (0xFD93, 'M', u'همج'), + (0xFD94, 'M', u'همم'), + (0xFD95, 'M', u'نحم'), + (0xFD96, 'M', u'نحى'), + (0xFD97, 'M', u'نجم'), + (0xFD99, 'M', u'نجى'), + (0xFD9A, 'M', u'نمي'), + (0xFD9B, 'M', u'نمى'), + (0xFD9C, 'M', u'يمم'), + (0xFD9E, 'M', u'بخي'), + (0xFD9F, 'M', u'تجي'), + (0xFDA0, 'M', u'تجى'), + (0xFDA1, 'M', u'تخي'), + (0xFDA2, 'M', u'تخى'), + (0xFDA3, 'M', u'تمي'), + (0xFDA4, 'M', u'تمى'), + (0xFDA5, 'M', u'جمي'), + (0xFDA6, 'M', u'جحى'), + (0xFDA7, 'M', u'جمى'), + (0xFDA8, 'M', u'سخى'), + (0xFDA9, 'M', u'صحي'), + (0xFDAA, 'M', u'شحي'), + (0xFDAB, 'M', u'ضحي'), + (0xFDAC, 'M', u'لجي'), + (0xFDAD, 'M', u'لمي'), + (0xFDAE, 'M', u'يحي'), + (0xFDAF, 'M', u'يجي'), + (0xFDB0, 'M', u'يمي'), + (0xFDB1, 'M', u'ممي'), + (0xFDB2, 'M', u'قمي'), + (0xFDB3, 'M', u'نحي'), + (0xFDB4, 'M', u'قمح'), + (0xFDB5, 'M', u'لحم'), + (0xFDB6, 'M', u'عمي'), + (0xFDB7, 'M', u'كمي'), + (0xFDB8, 'M', u'نجح'), + (0xFDB9, 'M', u'مخي'), + (0xFDBA, 'M', u'لجم'), + (0xFDBB, 'M', u'كمم'), + (0xFDBC, 'M', u'لجم'), + (0xFDBD, 'M', u'نجح'), + (0xFDBE, 'M', u'جحي'), + (0xFDBF, 'M', u'حجي'), + (0xFDC0, 'M', u'مجي'), + (0xFDC1, 'M', u'فمي'), + (0xFDC2, 'M', u'بحي'), + (0xFDC3, 'M', u'كمم'), + (0xFDC4, 'M', u'عجم'), + (0xFDC5, 'M', u'صمم'), + (0xFDC6, 'M', u'سخي'), + (0xFDC7, 'M', u'نجي'), + (0xFDC8, 'X'), + (0xFDF0, 'M', u'صلے'), + (0xFDF1, 'M', u'قلے'), + (0xFDF2, 'M', u'الله'), + (0xFDF3, 'M', u'اكبر'), + (0xFDF4, 'M', u'محمد'), + (0xFDF5, 'M', u'صلعم'), + (0xFDF6, 'M', u'رسول'), + (0xFDF7, 'M', u'عليه'), + (0xFDF8, 'M', u'وسلم'), + (0xFDF9, 'M', u'صلى'), + (0xFDFA, '3', u'صلى الله عليه وسلم'), + (0xFDFB, '3', u'جل جلاله'), + (0xFDFC, 'M', u'ریال'), + (0xFDFD, 'V'), + (0xFDFE, 'X'), + (0xFE00, 'I'), + (0xFE10, '3', u','), + ] + +def _seg_49(): + return [ + (0xFE11, 'M', u'、'), + (0xFE12, 'X'), + (0xFE13, '3', u':'), + (0xFE14, '3', u';'), + (0xFE15, '3', u'!'), + (0xFE16, '3', u'?'), + (0xFE17, 'M', u'〖'), + (0xFE18, 'M', u'〗'), + (0xFE19, 'X'), + (0xFE20, 'V'), + (0xFE30, 'X'), + (0xFE31, 'M', u'—'), + (0xFE32, 'M', u'–'), + (0xFE33, '3', u'_'), + (0xFE35, '3', u'('), + (0xFE36, '3', u')'), + (0xFE37, '3', u'{'), + (0xFE38, '3', u'}'), + (0xFE39, 'M', u'〔'), + (0xFE3A, 'M', u'〕'), + (0xFE3B, 'M', u'【'), + (0xFE3C, 'M', u'】'), + (0xFE3D, 'M', u'《'), + (0xFE3E, 'M', u'》'), + (0xFE3F, 'M', u'〈'), + (0xFE40, 'M', u'〉'), + (0xFE41, 'M', u'「'), + (0xFE42, 'M', u'」'), + (0xFE43, 'M', u'『'), + (0xFE44, 'M', u'』'), + (0xFE45, 'V'), + (0xFE47, '3', u'['), + (0xFE48, '3', u']'), + (0xFE49, '3', u' ̅'), + (0xFE4D, '3', u'_'), + (0xFE50, '3', u','), + (0xFE51, 'M', u'、'), + (0xFE52, 'X'), + (0xFE54, '3', u';'), + (0xFE55, '3', u':'), + (0xFE56, '3', u'?'), + (0xFE57, '3', u'!'), + (0xFE58, 'M', u'—'), + (0xFE59, '3', u'('), + (0xFE5A, '3', u')'), + (0xFE5B, '3', u'{'), + (0xFE5C, '3', u'}'), + (0xFE5D, 'M', u'〔'), + (0xFE5E, 'M', u'〕'), + (0xFE5F, '3', u'#'), + (0xFE60, '3', u'&'), + (0xFE61, '3', u'*'), + (0xFE62, '3', u'+'), + (0xFE63, 'M', u'-'), + (0xFE64, '3', u'<'), + (0xFE65, '3', u'>'), + (0xFE66, '3', u'='), + (0xFE67, 'X'), + (0xFE68, '3', u'\\'), + (0xFE69, '3', u'$'), + (0xFE6A, '3', u'%'), + (0xFE6B, '3', u'@'), + (0xFE6C, 'X'), + (0xFE70, '3', u' ً'), + (0xFE71, 'M', u'ـً'), + (0xFE72, '3', u' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', u' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', u' َ'), + (0xFE77, 'M', u'ـَ'), + (0xFE78, '3', u' ُ'), + (0xFE79, 'M', u'ـُ'), + (0xFE7A, '3', u' ِ'), + (0xFE7B, 'M', u'ـِ'), + (0xFE7C, '3', u' ّ'), + (0xFE7D, 'M', u'ـّ'), + (0xFE7E, '3', u' ْ'), + (0xFE7F, 'M', u'ـْ'), + (0xFE80, 'M', u'ء'), + (0xFE81, 'M', u'آ'), + (0xFE83, 'M', u'أ'), + (0xFE85, 'M', u'ؤ'), + (0xFE87, 'M', u'إ'), + (0xFE89, 'M', u'ئ'), + (0xFE8D, 'M', u'ا'), + (0xFE8F, 'M', u'ب'), + (0xFE93, 'M', u'ة'), + (0xFE95, 'M', u'ت'), + (0xFE99, 'M', u'ث'), + (0xFE9D, 'M', u'ج'), + (0xFEA1, 'M', u'ح'), + (0xFEA5, 'M', u'خ'), + (0xFEA9, 'M', u'د'), + (0xFEAB, 'M', u'ذ'), + (0xFEAD, 'M', u'ر'), + (0xFEAF, 'M', u'ز'), + (0xFEB1, 'M', u'س'), + (0xFEB5, 'M', u'ش'), + (0xFEB9, 'M', u'ص'), + ] + +def _seg_50(): + return [ + (0xFEBD, 'M', u'ض'), + (0xFEC1, 'M', u'ط'), + (0xFEC5, 'M', u'ظ'), + (0xFEC9, 'M', u'ع'), + (0xFECD, 'M', u'غ'), + (0xFED1, 'M', u'ف'), + (0xFED5, 'M', u'ق'), + (0xFED9, 'M', u'ك'), + (0xFEDD, 'M', u'ل'), + (0xFEE1, 'M', u'م'), + (0xFEE5, 'M', u'ن'), + (0xFEE9, 'M', u'ه'), + (0xFEED, 'M', u'و'), + (0xFEEF, 'M', u'ى'), + (0xFEF1, 'M', u'ي'), + (0xFEF5, 'M', u'لآ'), + (0xFEF7, 'M', u'لأ'), + (0xFEF9, 'M', u'لإ'), + (0xFEFB, 'M', u'لا'), + (0xFEFD, 'X'), + (0xFEFF, 'I'), + (0xFF00, 'X'), + (0xFF01, '3', u'!'), + (0xFF02, '3', u'"'), + (0xFF03, '3', u'#'), + (0xFF04, '3', u'$'), + (0xFF05, '3', u'%'), + (0xFF06, '3', u'&'), + (0xFF07, '3', u'\''), + (0xFF08, '3', u'('), + (0xFF09, '3', u')'), + (0xFF0A, '3', u'*'), + (0xFF0B, '3', u'+'), + (0xFF0C, '3', u','), + (0xFF0D, 'M', u'-'), + (0xFF0E, 'M', u'.'), + (0xFF0F, '3', u'/'), + (0xFF10, 'M', u'0'), + (0xFF11, 'M', u'1'), + (0xFF12, 'M', u'2'), + (0xFF13, 'M', u'3'), + (0xFF14, 'M', u'4'), + (0xFF15, 'M', u'5'), + (0xFF16, 'M', u'6'), + (0xFF17, 'M', u'7'), + (0xFF18, 'M', u'8'), + (0xFF19, 'M', u'9'), + (0xFF1A, '3', u':'), + (0xFF1B, '3', u';'), + (0xFF1C, '3', u'<'), + (0xFF1D, '3', u'='), + (0xFF1E, '3', u'>'), + (0xFF1F, '3', u'?'), + (0xFF20, '3', u'@'), + (0xFF21, 'M', u'a'), + (0xFF22, 'M', u'b'), + (0xFF23, 'M', u'c'), + (0xFF24, 'M', u'd'), + (0xFF25, 'M', u'e'), + (0xFF26, 'M', u'f'), + (0xFF27, 'M', u'g'), + (0xFF28, 'M', u'h'), + (0xFF29, 'M', u'i'), + (0xFF2A, 'M', u'j'), + (0xFF2B, 'M', u'k'), + (0xFF2C, 'M', u'l'), + (0xFF2D, 'M', u'm'), + (0xFF2E, 'M', u'n'), + (0xFF2F, 'M', u'o'), + (0xFF30, 'M', u'p'), + (0xFF31, 'M', u'q'), + (0xFF32, 'M', u'r'), + (0xFF33, 'M', u's'), + (0xFF34, 'M', u't'), + (0xFF35, 'M', u'u'), + (0xFF36, 'M', u'v'), + (0xFF37, 'M', u'w'), + (0xFF38, 'M', u'x'), + (0xFF39, 'M', u'y'), + (0xFF3A, 'M', u'z'), + (0xFF3B, '3', u'['), + (0xFF3C, '3', u'\\'), + (0xFF3D, '3', u']'), + (0xFF3E, '3', u'^'), + (0xFF3F, '3', u'_'), + (0xFF40, '3', u'`'), + (0xFF41, 'M', u'a'), + (0xFF42, 'M', u'b'), + (0xFF43, 'M', u'c'), + (0xFF44, 'M', u'd'), + (0xFF45, 'M', u'e'), + (0xFF46, 'M', u'f'), + (0xFF47, 'M', u'g'), + (0xFF48, 'M', u'h'), + (0xFF49, 'M', u'i'), + (0xFF4A, 'M', u'j'), + (0xFF4B, 'M', u'k'), + (0xFF4C, 'M', u'l'), + (0xFF4D, 'M', u'm'), + (0xFF4E, 'M', u'n'), + ] + +def _seg_51(): + return [ + (0xFF4F, 'M', u'o'), + (0xFF50, 'M', u'p'), + (0xFF51, 'M', u'q'), + (0xFF52, 'M', u'r'), + (0xFF53, 'M', u's'), + (0xFF54, 'M', u't'), + (0xFF55, 'M', u'u'), + (0xFF56, 'M', u'v'), + (0xFF57, 'M', u'w'), + (0xFF58, 'M', u'x'), + (0xFF59, 'M', u'y'), + (0xFF5A, 'M', u'z'), + (0xFF5B, '3', u'{'), + (0xFF5C, '3', u'|'), + (0xFF5D, '3', u'}'), + (0xFF5E, '3', u'~'), + (0xFF5F, 'M', u'⦅'), + (0xFF60, 'M', u'⦆'), + (0xFF61, 'M', u'.'), + (0xFF62, 'M', u'「'), + (0xFF63, 'M', u'」'), + (0xFF64, 'M', u'、'), + (0xFF65, 'M', u'・'), + (0xFF66, 'M', u'ヲ'), + (0xFF67, 'M', u'ァ'), + (0xFF68, 'M', u'ィ'), + (0xFF69, 'M', u'ゥ'), + (0xFF6A, 'M', u'ェ'), + (0xFF6B, 'M', u'ォ'), + (0xFF6C, 'M', u'ャ'), + (0xFF6D, 'M', u'ュ'), + (0xFF6E, 'M', u'ョ'), + (0xFF6F, 'M', u'ッ'), + (0xFF70, 'M', u'ー'), + (0xFF71, 'M', u'ア'), + (0xFF72, 'M', u'イ'), + (0xFF73, 'M', u'ウ'), + (0xFF74, 'M', u'エ'), + (0xFF75, 'M', u'オ'), + (0xFF76, 'M', u'カ'), + (0xFF77, 'M', u'キ'), + (0xFF78, 'M', u'ク'), + (0xFF79, 'M', u'ケ'), + (0xFF7A, 'M', u'コ'), + (0xFF7B, 'M', u'サ'), + (0xFF7C, 'M', u'シ'), + (0xFF7D, 'M', u'ス'), + (0xFF7E, 'M', u'セ'), + (0xFF7F, 'M', u'ソ'), + (0xFF80, 'M', u'タ'), + (0xFF81, 'M', u'チ'), + (0xFF82, 'M', u'ツ'), + (0xFF83, 'M', u'テ'), + (0xFF84, 'M', u'ト'), + (0xFF85, 'M', u'ナ'), + (0xFF86, 'M', u'ニ'), + (0xFF87, 'M', u'ヌ'), + (0xFF88, 'M', u'ネ'), + (0xFF89, 'M', u'ノ'), + (0xFF8A, 'M', u'ハ'), + (0xFF8B, 'M', u'ヒ'), + (0xFF8C, 'M', u'フ'), + (0xFF8D, 'M', u'ヘ'), + (0xFF8E, 'M', u'ホ'), + (0xFF8F, 'M', u'マ'), + (0xFF90, 'M', u'ミ'), + (0xFF91, 'M', u'ム'), + (0xFF92, 'M', u'メ'), + (0xFF93, 'M', u'モ'), + (0xFF94, 'M', u'ヤ'), + (0xFF95, 'M', u'ユ'), + (0xFF96, 'M', u'ヨ'), + (0xFF97, 'M', u'ラ'), + (0xFF98, 'M', u'リ'), + (0xFF99, 'M', u'ル'), + (0xFF9A, 'M', u'レ'), + (0xFF9B, 'M', u'ロ'), + (0xFF9C, 'M', u'ワ'), + (0xFF9D, 'M', u'ン'), + (0xFF9E, 'M', u'゙'), + (0xFF9F, 'M', u'゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', u'ᄀ'), + (0xFFA2, 'M', u'ᄁ'), + (0xFFA3, 'M', u'ᆪ'), + (0xFFA4, 'M', u'ᄂ'), + (0xFFA5, 'M', u'ᆬ'), + (0xFFA6, 'M', u'ᆭ'), + (0xFFA7, 'M', u'ᄃ'), + (0xFFA8, 'M', u'ᄄ'), + (0xFFA9, 'M', u'ᄅ'), + (0xFFAA, 'M', u'ᆰ'), + (0xFFAB, 'M', u'ᆱ'), + (0xFFAC, 'M', u'ᆲ'), + (0xFFAD, 'M', u'ᆳ'), + (0xFFAE, 'M', u'ᆴ'), + (0xFFAF, 'M', u'ᆵ'), + (0xFFB0, 'M', u'ᄚ'), + (0xFFB1, 'M', u'ᄆ'), + (0xFFB2, 'M', u'ᄇ'), + ] + +def _seg_52(): + return [ + (0xFFB3, 'M', u'ᄈ'), + (0xFFB4, 'M', u'ᄡ'), + (0xFFB5, 'M', u'ᄉ'), + (0xFFB6, 'M', u'ᄊ'), + (0xFFB7, 'M', u'ᄋ'), + (0xFFB8, 'M', u'ᄌ'), + (0xFFB9, 'M', u'ᄍ'), + (0xFFBA, 'M', u'ᄎ'), + (0xFFBB, 'M', u'ᄏ'), + (0xFFBC, 'M', u'ᄐ'), + (0xFFBD, 'M', u'ᄑ'), + (0xFFBE, 'M', u'ᄒ'), + (0xFFBF, 'X'), + (0xFFC2, 'M', u'ᅡ'), + (0xFFC3, 'M', u'ᅢ'), + (0xFFC4, 'M', u'ᅣ'), + (0xFFC5, 'M', u'ᅤ'), + (0xFFC6, 'M', u'ᅥ'), + (0xFFC7, 'M', u'ᅦ'), + (0xFFC8, 'X'), + (0xFFCA, 'M', u'ᅧ'), + (0xFFCB, 'M', u'ᅨ'), + (0xFFCC, 'M', u'ᅩ'), + (0xFFCD, 'M', u'ᅪ'), + (0xFFCE, 'M', u'ᅫ'), + (0xFFCF, 'M', u'ᅬ'), + (0xFFD0, 'X'), + (0xFFD2, 'M', u'ᅭ'), + (0xFFD3, 'M', u'ᅮ'), + (0xFFD4, 'M', u'ᅯ'), + (0xFFD5, 'M', u'ᅰ'), + (0xFFD6, 'M', u'ᅱ'), + (0xFFD7, 'M', u'ᅲ'), + (0xFFD8, 'X'), + (0xFFDA, 'M', u'ᅳ'), + (0xFFDB, 'M', u'ᅴ'), + (0xFFDC, 'M', u'ᅵ'), + (0xFFDD, 'X'), + (0xFFE0, 'M', u'¢'), + (0xFFE1, 'M', u'£'), + (0xFFE2, 'M', u'¬'), + (0xFFE3, '3', u' ̄'), + (0xFFE4, 'M', u'¦'), + (0xFFE5, 'M', u'¥'), + (0xFFE6, 'M', u'₩'), + (0xFFE7, 'X'), + (0xFFE8, 'M', u'│'), + (0xFFE9, 'M', u'←'), + (0xFFEA, 'M', u'↑'), + (0xFFEB, 'M', u'→'), + (0xFFEC, 'M', u'↓'), + (0xFFED, 'M', u'■'), + (0xFFEE, 'M', u'○'), + (0xFFEF, 'X'), + (0x10000, 'V'), + (0x1000C, 'X'), + (0x1000D, 'V'), + (0x10027, 'X'), + (0x10028, 'V'), + (0x1003B, 'X'), + (0x1003C, 'V'), + (0x1003E, 'X'), + (0x1003F, 'V'), + (0x1004E, 'X'), + (0x10050, 'V'), + (0x1005E, 'X'), + (0x10080, 'V'), + (0x100FB, 'X'), + (0x10100, 'V'), + (0x10103, 'X'), + (0x10107, 'V'), + (0x10134, 'X'), + (0x10137, 'V'), + (0x1018F, 'X'), + (0x10190, 'V'), + (0x1019C, 'X'), + (0x101A0, 'V'), + (0x101A1, 'X'), + (0x101D0, 'V'), + (0x101FE, 'X'), + (0x10280, 'V'), + (0x1029D, 'X'), + (0x102A0, 'V'), + (0x102D1, 'X'), + (0x102E0, 'V'), + (0x102FC, 'X'), + (0x10300, 'V'), + (0x10324, 'X'), + (0x1032D, 'V'), + (0x1034B, 'X'), + (0x10350, 'V'), + (0x1037B, 'X'), + (0x10380, 'V'), + (0x1039E, 'X'), + (0x1039F, 'V'), + (0x103C4, 'X'), + (0x103C8, 'V'), + (0x103D6, 'X'), + (0x10400, 'M', u'𐐨'), + (0x10401, 'M', u'𐐩'), + ] + +def _seg_53(): + return [ + (0x10402, 'M', u'𐐪'), + (0x10403, 'M', u'𐐫'), + (0x10404, 'M', u'𐐬'), + (0x10405, 'M', u'𐐭'), + (0x10406, 'M', u'𐐮'), + (0x10407, 'M', u'𐐯'), + (0x10408, 'M', u'𐐰'), + (0x10409, 'M', u'𐐱'), + (0x1040A, 'M', u'𐐲'), + (0x1040B, 'M', u'𐐳'), + (0x1040C, 'M', u'𐐴'), + (0x1040D, 'M', u'𐐵'), + (0x1040E, 'M', u'𐐶'), + (0x1040F, 'M', u'𐐷'), + (0x10410, 'M', u'𐐸'), + (0x10411, 'M', u'𐐹'), + (0x10412, 'M', u'𐐺'), + (0x10413, 'M', u'𐐻'), + (0x10414, 'M', u'𐐼'), + (0x10415, 'M', u'𐐽'), + (0x10416, 'M', u'𐐾'), + (0x10417, 'M', u'𐐿'), + (0x10418, 'M', u'𐑀'), + (0x10419, 'M', u'𐑁'), + (0x1041A, 'M', u'𐑂'), + (0x1041B, 'M', u'𐑃'), + (0x1041C, 'M', u'𐑄'), + (0x1041D, 'M', u'𐑅'), + (0x1041E, 'M', u'𐑆'), + (0x1041F, 'M', u'𐑇'), + (0x10420, 'M', u'𐑈'), + (0x10421, 'M', u'𐑉'), + (0x10422, 'M', u'𐑊'), + (0x10423, 'M', u'𐑋'), + (0x10424, 'M', u'𐑌'), + (0x10425, 'M', u'𐑍'), + (0x10426, 'M', u'𐑎'), + (0x10427, 'M', u'𐑏'), + (0x10428, 'V'), + (0x1049E, 'X'), + (0x104A0, 'V'), + (0x104AA, 'X'), + (0x104B0, 'M', u'𐓘'), + (0x104B1, 'M', u'𐓙'), + (0x104B2, 'M', u'𐓚'), + (0x104B3, 'M', u'𐓛'), + (0x104B4, 'M', u'𐓜'), + (0x104B5, 'M', u'𐓝'), + (0x104B6, 'M', u'𐓞'), + (0x104B7, 'M', u'𐓟'), + (0x104B8, 'M', u'𐓠'), + (0x104B9, 'M', u'𐓡'), + (0x104BA, 'M', u'𐓢'), + (0x104BB, 'M', u'𐓣'), + (0x104BC, 'M', u'𐓤'), + (0x104BD, 'M', u'𐓥'), + (0x104BE, 'M', u'𐓦'), + (0x104BF, 'M', u'𐓧'), + (0x104C0, 'M', u'𐓨'), + (0x104C1, 'M', u'𐓩'), + (0x104C2, 'M', u'𐓪'), + (0x104C3, 'M', u'𐓫'), + (0x104C4, 'M', u'𐓬'), + (0x104C5, 'M', u'𐓭'), + (0x104C6, 'M', u'𐓮'), + (0x104C7, 'M', u'𐓯'), + (0x104C8, 'M', u'𐓰'), + (0x104C9, 'M', u'𐓱'), + (0x104CA, 'M', u'𐓲'), + (0x104CB, 'M', u'𐓳'), + (0x104CC, 'M', u'𐓴'), + (0x104CD, 'M', u'𐓵'), + (0x104CE, 'M', u'𐓶'), + (0x104CF, 'M', u'𐓷'), + (0x104D0, 'M', u'𐓸'), + (0x104D1, 'M', u'𐓹'), + (0x104D2, 'M', u'𐓺'), + (0x104D3, 'M', u'𐓻'), + (0x104D4, 'X'), + (0x104D8, 'V'), + (0x104FC, 'X'), + (0x10500, 'V'), + (0x10528, 'X'), + (0x10530, 'V'), + (0x10564, 'X'), + (0x1056F, 'V'), + (0x10570, 'X'), + (0x10600, 'V'), + (0x10737, 'X'), + (0x10740, 'V'), + (0x10756, 'X'), + (0x10760, 'V'), + (0x10768, 'X'), + (0x10800, 'V'), + (0x10806, 'X'), + (0x10808, 'V'), + (0x10809, 'X'), + (0x1080A, 'V'), + (0x10836, 'X'), + (0x10837, 'V'), + ] + +def _seg_54(): + return [ + (0x10839, 'X'), + (0x1083C, 'V'), + (0x1083D, 'X'), + (0x1083F, 'V'), + (0x10856, 'X'), + (0x10857, 'V'), + (0x1089F, 'X'), + (0x108A7, 'V'), + (0x108B0, 'X'), + (0x108E0, 'V'), + (0x108F3, 'X'), + (0x108F4, 'V'), + (0x108F6, 'X'), + (0x108FB, 'V'), + (0x1091C, 'X'), + (0x1091F, 'V'), + (0x1093A, 'X'), + (0x1093F, 'V'), + (0x10940, 'X'), + (0x10980, 'V'), + (0x109B8, 'X'), + (0x109BC, 'V'), + (0x109D0, 'X'), + (0x109D2, 'V'), + (0x10A04, 'X'), + (0x10A05, 'V'), + (0x10A07, 'X'), + (0x10A0C, 'V'), + (0x10A14, 'X'), + (0x10A15, 'V'), + (0x10A18, 'X'), + (0x10A19, 'V'), + (0x10A36, 'X'), + (0x10A38, 'V'), + (0x10A3B, 'X'), + (0x10A3F, 'V'), + (0x10A49, 'X'), + (0x10A50, 'V'), + (0x10A59, 'X'), + (0x10A60, 'V'), + (0x10AA0, 'X'), + (0x10AC0, 'V'), + (0x10AE7, 'X'), + (0x10AEB, 'V'), + (0x10AF7, 'X'), + (0x10B00, 'V'), + (0x10B36, 'X'), + (0x10B39, 'V'), + (0x10B56, 'X'), + (0x10B58, 'V'), + (0x10B73, 'X'), + (0x10B78, 'V'), + (0x10B92, 'X'), + (0x10B99, 'V'), + (0x10B9D, 'X'), + (0x10BA9, 'V'), + (0x10BB0, 'X'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', u'𐳀'), + (0x10C81, 'M', u'𐳁'), + (0x10C82, 'M', u'𐳂'), + (0x10C83, 'M', u'𐳃'), + (0x10C84, 'M', u'𐳄'), + (0x10C85, 'M', u'𐳅'), + (0x10C86, 'M', u'𐳆'), + (0x10C87, 'M', u'𐳇'), + (0x10C88, 'M', u'𐳈'), + (0x10C89, 'M', u'𐳉'), + (0x10C8A, 'M', u'𐳊'), + (0x10C8B, 'M', u'𐳋'), + (0x10C8C, 'M', u'𐳌'), + (0x10C8D, 'M', u'𐳍'), + (0x10C8E, 'M', u'𐳎'), + (0x10C8F, 'M', u'𐳏'), + (0x10C90, 'M', u'𐳐'), + (0x10C91, 'M', u'𐳑'), + (0x10C92, 'M', u'𐳒'), + (0x10C93, 'M', u'𐳓'), + (0x10C94, 'M', u'𐳔'), + (0x10C95, 'M', u'𐳕'), + (0x10C96, 'M', u'𐳖'), + (0x10C97, 'M', u'𐳗'), + (0x10C98, 'M', u'𐳘'), + (0x10C99, 'M', u'𐳙'), + (0x10C9A, 'M', u'𐳚'), + (0x10C9B, 'M', u'𐳛'), + (0x10C9C, 'M', u'𐳜'), + (0x10C9D, 'M', u'𐳝'), + (0x10C9E, 'M', u'𐳞'), + (0x10C9F, 'M', u'𐳟'), + (0x10CA0, 'M', u'𐳠'), + (0x10CA1, 'M', u'𐳡'), + (0x10CA2, 'M', u'𐳢'), + (0x10CA3, 'M', u'𐳣'), + (0x10CA4, 'M', u'𐳤'), + (0x10CA5, 'M', u'𐳥'), + (0x10CA6, 'M', u'𐳦'), + (0x10CA7, 'M', u'𐳧'), + (0x10CA8, 'M', u'𐳨'), + ] + +def _seg_55(): + return [ + (0x10CA9, 'M', u'𐳩'), + (0x10CAA, 'M', u'𐳪'), + (0x10CAB, 'M', u'𐳫'), + (0x10CAC, 'M', u'𐳬'), + (0x10CAD, 'M', u'𐳭'), + (0x10CAE, 'M', u'𐳮'), + (0x10CAF, 'M', u'𐳯'), + (0x10CB0, 'M', u'𐳰'), + (0x10CB1, 'M', u'𐳱'), + (0x10CB2, 'M', u'𐳲'), + (0x10CB3, 'X'), + (0x10CC0, 'V'), + (0x10CF3, 'X'), + (0x10CFA, 'V'), + (0x10D28, 'X'), + (0x10D30, 'V'), + (0x10D3A, 'X'), + (0x10E60, 'V'), + (0x10E7F, 'X'), + (0x10F00, 'V'), + (0x10F28, 'X'), + (0x10F30, 'V'), + (0x10F5A, 'X'), + (0x11000, 'V'), + (0x1104E, 'X'), + (0x11052, 'V'), + (0x11070, 'X'), + (0x1107F, 'V'), + (0x110BD, 'X'), + (0x110BE, 'V'), + (0x110C2, 'X'), + (0x110D0, 'V'), + (0x110E9, 'X'), + (0x110F0, 'V'), + (0x110FA, 'X'), + (0x11100, 'V'), + (0x11135, 'X'), + (0x11136, 'V'), + (0x11147, 'X'), + (0x11150, 'V'), + (0x11177, 'X'), + (0x11180, 'V'), + (0x111CE, 'X'), + (0x111D0, 'V'), + (0x111E0, 'X'), + (0x111E1, 'V'), + (0x111F5, 'X'), + (0x11200, 'V'), + (0x11212, 'X'), + (0x11213, 'V'), + (0x1123F, 'X'), + (0x11280, 'V'), + (0x11287, 'X'), + (0x11288, 'V'), + (0x11289, 'X'), + (0x1128A, 'V'), + (0x1128E, 'X'), + (0x1128F, 'V'), + (0x1129E, 'X'), + (0x1129F, 'V'), + (0x112AA, 'X'), + (0x112B0, 'V'), + (0x112EB, 'X'), + (0x112F0, 'V'), + (0x112FA, 'X'), + (0x11300, 'V'), + (0x11304, 'X'), + (0x11305, 'V'), + (0x1130D, 'X'), + (0x1130F, 'V'), + (0x11311, 'X'), + (0x11313, 'V'), + (0x11329, 'X'), + (0x1132A, 'V'), + (0x11331, 'X'), + (0x11332, 'V'), + (0x11334, 'X'), + (0x11335, 'V'), + (0x1133A, 'X'), + (0x1133B, 'V'), + (0x11345, 'X'), + (0x11347, 'V'), + (0x11349, 'X'), + (0x1134B, 'V'), + (0x1134E, 'X'), + (0x11350, 'V'), + (0x11351, 'X'), + (0x11357, 'V'), + (0x11358, 'X'), + (0x1135D, 'V'), + (0x11364, 'X'), + (0x11366, 'V'), + (0x1136D, 'X'), + (0x11370, 'V'), + (0x11375, 'X'), + (0x11400, 'V'), + (0x1145A, 'X'), + (0x1145B, 'V'), + (0x1145C, 'X'), + (0x1145D, 'V'), + ] + +def _seg_56(): + return [ + (0x1145F, 'X'), + (0x11480, 'V'), + (0x114C8, 'X'), + (0x114D0, 'V'), + (0x114DA, 'X'), + (0x11580, 'V'), + (0x115B6, 'X'), + (0x115B8, 'V'), + (0x115DE, 'X'), + (0x11600, 'V'), + (0x11645, 'X'), + (0x11650, 'V'), + (0x1165A, 'X'), + (0x11660, 'V'), + (0x1166D, 'X'), + (0x11680, 'V'), + (0x116B8, 'X'), + (0x116C0, 'V'), + (0x116CA, 'X'), + (0x11700, 'V'), + (0x1171B, 'X'), + (0x1171D, 'V'), + (0x1172C, 'X'), + (0x11730, 'V'), + (0x11740, 'X'), + (0x11800, 'V'), + (0x1183C, 'X'), + (0x118A0, 'M', u'𑣀'), + (0x118A1, 'M', u'𑣁'), + (0x118A2, 'M', u'𑣂'), + (0x118A3, 'M', u'𑣃'), + (0x118A4, 'M', u'𑣄'), + (0x118A5, 'M', u'𑣅'), + (0x118A6, 'M', u'𑣆'), + (0x118A7, 'M', u'𑣇'), + (0x118A8, 'M', u'𑣈'), + (0x118A9, 'M', u'𑣉'), + (0x118AA, 'M', u'𑣊'), + (0x118AB, 'M', u'𑣋'), + (0x118AC, 'M', u'𑣌'), + (0x118AD, 'M', u'𑣍'), + (0x118AE, 'M', u'𑣎'), + (0x118AF, 'M', u'𑣏'), + (0x118B0, 'M', u'𑣐'), + (0x118B1, 'M', u'𑣑'), + (0x118B2, 'M', u'𑣒'), + (0x118B3, 'M', u'𑣓'), + (0x118B4, 'M', u'𑣔'), + (0x118B5, 'M', u'𑣕'), + (0x118B6, 'M', u'𑣖'), + (0x118B7, 'M', u'𑣗'), + (0x118B8, 'M', u'𑣘'), + (0x118B9, 'M', u'𑣙'), + (0x118BA, 'M', u'𑣚'), + (0x118BB, 'M', u'𑣛'), + (0x118BC, 'M', u'𑣜'), + (0x118BD, 'M', u'𑣝'), + (0x118BE, 'M', u'𑣞'), + (0x118BF, 'M', u'𑣟'), + (0x118C0, 'V'), + (0x118F3, 'X'), + (0x118FF, 'V'), + (0x11900, 'X'), + (0x11A00, 'V'), + (0x11A48, 'X'), + (0x11A50, 'V'), + (0x11A84, 'X'), + (0x11A86, 'V'), + (0x11AA3, 'X'), + (0x11AC0, 'V'), + (0x11AF9, 'X'), + (0x11C00, 'V'), + (0x11C09, 'X'), + (0x11C0A, 'V'), + (0x11C37, 'X'), + (0x11C38, 'V'), + (0x11C46, 'X'), + (0x11C50, 'V'), + (0x11C6D, 'X'), + (0x11C70, 'V'), + (0x11C90, 'X'), + (0x11C92, 'V'), + (0x11CA8, 'X'), + (0x11CA9, 'V'), + (0x11CB7, 'X'), + (0x11D00, 'V'), + (0x11D07, 'X'), + (0x11D08, 'V'), + (0x11D0A, 'X'), + (0x11D0B, 'V'), + (0x11D37, 'X'), + (0x11D3A, 'V'), + (0x11D3B, 'X'), + (0x11D3C, 'V'), + (0x11D3E, 'X'), + (0x11D3F, 'V'), + (0x11D48, 'X'), + (0x11D50, 'V'), + (0x11D5A, 'X'), + (0x11D60, 'V'), + ] + +def _seg_57(): + return [ + (0x11D66, 'X'), + (0x11D67, 'V'), + (0x11D69, 'X'), + (0x11D6A, 'V'), + (0x11D8F, 'X'), + (0x11D90, 'V'), + (0x11D92, 'X'), + (0x11D93, 'V'), + (0x11D99, 'X'), + (0x11DA0, 'V'), + (0x11DAA, 'X'), + (0x11EE0, 'V'), + (0x11EF9, 'X'), + (0x12000, 'V'), + (0x1239A, 'X'), + (0x12400, 'V'), + (0x1246F, 'X'), + (0x12470, 'V'), + (0x12475, 'X'), + (0x12480, 'V'), + (0x12544, 'X'), + (0x13000, 'V'), + (0x1342F, 'X'), + (0x14400, 'V'), + (0x14647, 'X'), + (0x16800, 'V'), + (0x16A39, 'X'), + (0x16A40, 'V'), + (0x16A5F, 'X'), + (0x16A60, 'V'), + (0x16A6A, 'X'), + (0x16A6E, 'V'), + (0x16A70, 'X'), + (0x16AD0, 'V'), + (0x16AEE, 'X'), + (0x16AF0, 'V'), + (0x16AF6, 'X'), + (0x16B00, 'V'), + (0x16B46, 'X'), + (0x16B50, 'V'), + (0x16B5A, 'X'), + (0x16B5B, 'V'), + (0x16B62, 'X'), + (0x16B63, 'V'), + (0x16B78, 'X'), + (0x16B7D, 'V'), + (0x16B90, 'X'), + (0x16E60, 'V'), + (0x16E9B, 'X'), + (0x16F00, 'V'), + (0x16F45, 'X'), + (0x16F50, 'V'), + (0x16F7F, 'X'), + (0x16F8F, 'V'), + (0x16FA0, 'X'), + (0x16FE0, 'V'), + (0x16FE2, 'X'), + (0x17000, 'V'), + (0x187F2, 'X'), + (0x18800, 'V'), + (0x18AF3, 'X'), + (0x1B000, 'V'), + (0x1B11F, 'X'), + (0x1B170, 'V'), + (0x1B2FC, 'X'), + (0x1BC00, 'V'), + (0x1BC6B, 'X'), + (0x1BC70, 'V'), + (0x1BC7D, 'X'), + (0x1BC80, 'V'), + (0x1BC89, 'X'), + (0x1BC90, 'V'), + (0x1BC9A, 'X'), + (0x1BC9C, 'V'), + (0x1BCA0, 'I'), + (0x1BCA4, 'X'), + (0x1D000, 'V'), + (0x1D0F6, 'X'), + (0x1D100, 'V'), + (0x1D127, 'X'), + (0x1D129, 'V'), + (0x1D15E, 'M', u'𝅗𝅥'), + (0x1D15F, 'M', u'𝅘𝅥'), + (0x1D160, 'M', u'𝅘𝅥𝅮'), + (0x1D161, 'M', u'𝅘𝅥𝅯'), + (0x1D162, 'M', u'𝅘𝅥𝅰'), + (0x1D163, 'M', u'𝅘𝅥𝅱'), + (0x1D164, 'M', u'𝅘𝅥𝅲'), + (0x1D165, 'V'), + (0x1D173, 'X'), + (0x1D17B, 'V'), + (0x1D1BB, 'M', u'𝆹𝅥'), + (0x1D1BC, 'M', u'𝆺𝅥'), + (0x1D1BD, 'M', u'𝆹𝅥𝅮'), + (0x1D1BE, 'M', u'𝆺𝅥𝅮'), + (0x1D1BF, 'M', u'𝆹𝅥𝅯'), + (0x1D1C0, 'M', u'𝆺𝅥𝅯'), + (0x1D1C1, 'V'), + (0x1D1E9, 'X'), + (0x1D200, 'V'), + ] + +def _seg_58(): + return [ + (0x1D246, 'X'), + (0x1D2E0, 'V'), + (0x1D2F4, 'X'), + (0x1D300, 'V'), + (0x1D357, 'X'), + (0x1D360, 'V'), + (0x1D379, 'X'), + (0x1D400, 'M', u'a'), + (0x1D401, 'M', u'b'), + (0x1D402, 'M', u'c'), + (0x1D403, 'M', u'd'), + (0x1D404, 'M', u'e'), + (0x1D405, 'M', u'f'), + (0x1D406, 'M', u'g'), + (0x1D407, 'M', u'h'), + (0x1D408, 'M', u'i'), + (0x1D409, 'M', u'j'), + (0x1D40A, 'M', u'k'), + (0x1D40B, 'M', u'l'), + (0x1D40C, 'M', u'm'), + (0x1D40D, 'M', u'n'), + (0x1D40E, 'M', u'o'), + (0x1D40F, 'M', u'p'), + (0x1D410, 'M', u'q'), + (0x1D411, 'M', u'r'), + (0x1D412, 'M', u's'), + (0x1D413, 'M', u't'), + (0x1D414, 'M', u'u'), + (0x1D415, 'M', u'v'), + (0x1D416, 'M', u'w'), + (0x1D417, 'M', u'x'), + (0x1D418, 'M', u'y'), + (0x1D419, 'M', u'z'), + (0x1D41A, 'M', u'a'), + (0x1D41B, 'M', u'b'), + (0x1D41C, 'M', u'c'), + (0x1D41D, 'M', u'd'), + (0x1D41E, 'M', u'e'), + (0x1D41F, 'M', u'f'), + (0x1D420, 'M', u'g'), + (0x1D421, 'M', u'h'), + (0x1D422, 'M', u'i'), + (0x1D423, 'M', u'j'), + (0x1D424, 'M', u'k'), + (0x1D425, 'M', u'l'), + (0x1D426, 'M', u'm'), + (0x1D427, 'M', u'n'), + (0x1D428, 'M', u'o'), + (0x1D429, 'M', u'p'), + (0x1D42A, 'M', u'q'), + (0x1D42B, 'M', u'r'), + (0x1D42C, 'M', u's'), + (0x1D42D, 'M', u't'), + (0x1D42E, 'M', u'u'), + (0x1D42F, 'M', u'v'), + (0x1D430, 'M', u'w'), + (0x1D431, 'M', u'x'), + (0x1D432, 'M', u'y'), + (0x1D433, 'M', u'z'), + (0x1D434, 'M', u'a'), + (0x1D435, 'M', u'b'), + (0x1D436, 'M', u'c'), + (0x1D437, 'M', u'd'), + (0x1D438, 'M', u'e'), + (0x1D439, 'M', u'f'), + (0x1D43A, 'M', u'g'), + (0x1D43B, 'M', u'h'), + (0x1D43C, 'M', u'i'), + (0x1D43D, 'M', u'j'), + (0x1D43E, 'M', u'k'), + (0x1D43F, 'M', u'l'), + (0x1D440, 'M', u'm'), + (0x1D441, 'M', u'n'), + (0x1D442, 'M', u'o'), + (0x1D443, 'M', u'p'), + (0x1D444, 'M', u'q'), + (0x1D445, 'M', u'r'), + (0x1D446, 'M', u's'), + (0x1D447, 'M', u't'), + (0x1D448, 'M', u'u'), + (0x1D449, 'M', u'v'), + (0x1D44A, 'M', u'w'), + (0x1D44B, 'M', u'x'), + (0x1D44C, 'M', u'y'), + (0x1D44D, 'M', u'z'), + (0x1D44E, 'M', u'a'), + (0x1D44F, 'M', u'b'), + (0x1D450, 'M', u'c'), + (0x1D451, 'M', u'd'), + (0x1D452, 'M', u'e'), + (0x1D453, 'M', u'f'), + (0x1D454, 'M', u'g'), + (0x1D455, 'X'), + (0x1D456, 'M', u'i'), + (0x1D457, 'M', u'j'), + (0x1D458, 'M', u'k'), + (0x1D459, 'M', u'l'), + (0x1D45A, 'M', u'm'), + (0x1D45B, 'M', u'n'), + (0x1D45C, 'M', u'o'), + ] + +def _seg_59(): + return [ + (0x1D45D, 'M', u'p'), + (0x1D45E, 'M', u'q'), + (0x1D45F, 'M', u'r'), + (0x1D460, 'M', u's'), + (0x1D461, 'M', u't'), + (0x1D462, 'M', u'u'), + (0x1D463, 'M', u'v'), + (0x1D464, 'M', u'w'), + (0x1D465, 'M', u'x'), + (0x1D466, 'M', u'y'), + (0x1D467, 'M', u'z'), + (0x1D468, 'M', u'a'), + (0x1D469, 'M', u'b'), + (0x1D46A, 'M', u'c'), + (0x1D46B, 'M', u'd'), + (0x1D46C, 'M', u'e'), + (0x1D46D, 'M', u'f'), + (0x1D46E, 'M', u'g'), + (0x1D46F, 'M', u'h'), + (0x1D470, 'M', u'i'), + (0x1D471, 'M', u'j'), + (0x1D472, 'M', u'k'), + (0x1D473, 'M', u'l'), + (0x1D474, 'M', u'm'), + (0x1D475, 'M', u'n'), + (0x1D476, 'M', u'o'), + (0x1D477, 'M', u'p'), + (0x1D478, 'M', u'q'), + (0x1D479, 'M', u'r'), + (0x1D47A, 'M', u's'), + (0x1D47B, 'M', u't'), + (0x1D47C, 'M', u'u'), + (0x1D47D, 'M', u'v'), + (0x1D47E, 'M', u'w'), + (0x1D47F, 'M', u'x'), + (0x1D480, 'M', u'y'), + (0x1D481, 'M', u'z'), + (0x1D482, 'M', u'a'), + (0x1D483, 'M', u'b'), + (0x1D484, 'M', u'c'), + (0x1D485, 'M', u'd'), + (0x1D486, 'M', u'e'), + (0x1D487, 'M', u'f'), + (0x1D488, 'M', u'g'), + (0x1D489, 'M', u'h'), + (0x1D48A, 'M', u'i'), + (0x1D48B, 'M', u'j'), + (0x1D48C, 'M', u'k'), + (0x1D48D, 'M', u'l'), + (0x1D48E, 'M', u'm'), + (0x1D48F, 'M', u'n'), + (0x1D490, 'M', u'o'), + (0x1D491, 'M', u'p'), + (0x1D492, 'M', u'q'), + (0x1D493, 'M', u'r'), + (0x1D494, 'M', u's'), + (0x1D495, 'M', u't'), + (0x1D496, 'M', u'u'), + (0x1D497, 'M', u'v'), + (0x1D498, 'M', u'w'), + (0x1D499, 'M', u'x'), + (0x1D49A, 'M', u'y'), + (0x1D49B, 'M', u'z'), + (0x1D49C, 'M', u'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', u'c'), + (0x1D49F, 'M', u'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', u'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', u'j'), + (0x1D4A6, 'M', u'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', u'n'), + (0x1D4AA, 'M', u'o'), + (0x1D4AB, 'M', u'p'), + (0x1D4AC, 'M', u'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', u's'), + (0x1D4AF, 'M', u't'), + (0x1D4B0, 'M', u'u'), + (0x1D4B1, 'M', u'v'), + (0x1D4B2, 'M', u'w'), + (0x1D4B3, 'M', u'x'), + (0x1D4B4, 'M', u'y'), + (0x1D4B5, 'M', u'z'), + (0x1D4B6, 'M', u'a'), + (0x1D4B7, 'M', u'b'), + (0x1D4B8, 'M', u'c'), + (0x1D4B9, 'M', u'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', u'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', u'h'), + (0x1D4BE, 'M', u'i'), + (0x1D4BF, 'M', u'j'), + (0x1D4C0, 'M', u'k'), + (0x1D4C1, 'M', u'l'), + (0x1D4C2, 'M', u'm'), + (0x1D4C3, 'M', u'n'), + ] + +def _seg_60(): + return [ + (0x1D4C4, 'X'), + (0x1D4C5, 'M', u'p'), + (0x1D4C6, 'M', u'q'), + (0x1D4C7, 'M', u'r'), + (0x1D4C8, 'M', u's'), + (0x1D4C9, 'M', u't'), + (0x1D4CA, 'M', u'u'), + (0x1D4CB, 'M', u'v'), + (0x1D4CC, 'M', u'w'), + (0x1D4CD, 'M', u'x'), + (0x1D4CE, 'M', u'y'), + (0x1D4CF, 'M', u'z'), + (0x1D4D0, 'M', u'a'), + (0x1D4D1, 'M', u'b'), + (0x1D4D2, 'M', u'c'), + (0x1D4D3, 'M', u'd'), + (0x1D4D4, 'M', u'e'), + (0x1D4D5, 'M', u'f'), + (0x1D4D6, 'M', u'g'), + (0x1D4D7, 'M', u'h'), + (0x1D4D8, 'M', u'i'), + (0x1D4D9, 'M', u'j'), + (0x1D4DA, 'M', u'k'), + (0x1D4DB, 'M', u'l'), + (0x1D4DC, 'M', u'm'), + (0x1D4DD, 'M', u'n'), + (0x1D4DE, 'M', u'o'), + (0x1D4DF, 'M', u'p'), + (0x1D4E0, 'M', u'q'), + (0x1D4E1, 'M', u'r'), + (0x1D4E2, 'M', u's'), + (0x1D4E3, 'M', u't'), + (0x1D4E4, 'M', u'u'), + (0x1D4E5, 'M', u'v'), + (0x1D4E6, 'M', u'w'), + (0x1D4E7, 'M', u'x'), + (0x1D4E8, 'M', u'y'), + (0x1D4E9, 'M', u'z'), + (0x1D4EA, 'M', u'a'), + (0x1D4EB, 'M', u'b'), + (0x1D4EC, 'M', u'c'), + (0x1D4ED, 'M', u'd'), + (0x1D4EE, 'M', u'e'), + (0x1D4EF, 'M', u'f'), + (0x1D4F0, 'M', u'g'), + (0x1D4F1, 'M', u'h'), + (0x1D4F2, 'M', u'i'), + (0x1D4F3, 'M', u'j'), + (0x1D4F4, 'M', u'k'), + (0x1D4F5, 'M', u'l'), + (0x1D4F6, 'M', u'm'), + (0x1D4F7, 'M', u'n'), + (0x1D4F8, 'M', u'o'), + (0x1D4F9, 'M', u'p'), + (0x1D4FA, 'M', u'q'), + (0x1D4FB, 'M', u'r'), + (0x1D4FC, 'M', u's'), + (0x1D4FD, 'M', u't'), + (0x1D4FE, 'M', u'u'), + (0x1D4FF, 'M', u'v'), + (0x1D500, 'M', u'w'), + (0x1D501, 'M', u'x'), + (0x1D502, 'M', u'y'), + (0x1D503, 'M', u'z'), + (0x1D504, 'M', u'a'), + (0x1D505, 'M', u'b'), + (0x1D506, 'X'), + (0x1D507, 'M', u'd'), + (0x1D508, 'M', u'e'), + (0x1D509, 'M', u'f'), + (0x1D50A, 'M', u'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', u'j'), + (0x1D50E, 'M', u'k'), + (0x1D50F, 'M', u'l'), + (0x1D510, 'M', u'm'), + (0x1D511, 'M', u'n'), + (0x1D512, 'M', u'o'), + (0x1D513, 'M', u'p'), + (0x1D514, 'M', u'q'), + (0x1D515, 'X'), + (0x1D516, 'M', u's'), + (0x1D517, 'M', u't'), + (0x1D518, 'M', u'u'), + (0x1D519, 'M', u'v'), + (0x1D51A, 'M', u'w'), + (0x1D51B, 'M', u'x'), + (0x1D51C, 'M', u'y'), + (0x1D51D, 'X'), + (0x1D51E, 'M', u'a'), + (0x1D51F, 'M', u'b'), + (0x1D520, 'M', u'c'), + (0x1D521, 'M', u'd'), + (0x1D522, 'M', u'e'), + (0x1D523, 'M', u'f'), + (0x1D524, 'M', u'g'), + (0x1D525, 'M', u'h'), + (0x1D526, 'M', u'i'), + (0x1D527, 'M', u'j'), + (0x1D528, 'M', u'k'), + ] + +def _seg_61(): + return [ + (0x1D529, 'M', u'l'), + (0x1D52A, 'M', u'm'), + (0x1D52B, 'M', u'n'), + (0x1D52C, 'M', u'o'), + (0x1D52D, 'M', u'p'), + (0x1D52E, 'M', u'q'), + (0x1D52F, 'M', u'r'), + (0x1D530, 'M', u's'), + (0x1D531, 'M', u't'), + (0x1D532, 'M', u'u'), + (0x1D533, 'M', u'v'), + (0x1D534, 'M', u'w'), + (0x1D535, 'M', u'x'), + (0x1D536, 'M', u'y'), + (0x1D537, 'M', u'z'), + (0x1D538, 'M', u'a'), + (0x1D539, 'M', u'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', u'd'), + (0x1D53C, 'M', u'e'), + (0x1D53D, 'M', u'f'), + (0x1D53E, 'M', u'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', u'i'), + (0x1D541, 'M', u'j'), + (0x1D542, 'M', u'k'), + (0x1D543, 'M', u'l'), + (0x1D544, 'M', u'm'), + (0x1D545, 'X'), + (0x1D546, 'M', u'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', u's'), + (0x1D54B, 'M', u't'), + (0x1D54C, 'M', u'u'), + (0x1D54D, 'M', u'v'), + (0x1D54E, 'M', u'w'), + (0x1D54F, 'M', u'x'), + (0x1D550, 'M', u'y'), + (0x1D551, 'X'), + (0x1D552, 'M', u'a'), + (0x1D553, 'M', u'b'), + (0x1D554, 'M', u'c'), + (0x1D555, 'M', u'd'), + (0x1D556, 'M', u'e'), + (0x1D557, 'M', u'f'), + (0x1D558, 'M', u'g'), + (0x1D559, 'M', u'h'), + (0x1D55A, 'M', u'i'), + (0x1D55B, 'M', u'j'), + (0x1D55C, 'M', u'k'), + (0x1D55D, 'M', u'l'), + (0x1D55E, 'M', u'm'), + (0x1D55F, 'M', u'n'), + (0x1D560, 'M', u'o'), + (0x1D561, 'M', u'p'), + (0x1D562, 'M', u'q'), + (0x1D563, 'M', u'r'), + (0x1D564, 'M', u's'), + (0x1D565, 'M', u't'), + (0x1D566, 'M', u'u'), + (0x1D567, 'M', u'v'), + (0x1D568, 'M', u'w'), + (0x1D569, 'M', u'x'), + (0x1D56A, 'M', u'y'), + (0x1D56B, 'M', u'z'), + (0x1D56C, 'M', u'a'), + (0x1D56D, 'M', u'b'), + (0x1D56E, 'M', u'c'), + (0x1D56F, 'M', u'd'), + (0x1D570, 'M', u'e'), + (0x1D571, 'M', u'f'), + (0x1D572, 'M', u'g'), + (0x1D573, 'M', u'h'), + (0x1D574, 'M', u'i'), + (0x1D575, 'M', u'j'), + (0x1D576, 'M', u'k'), + (0x1D577, 'M', u'l'), + (0x1D578, 'M', u'm'), + (0x1D579, 'M', u'n'), + (0x1D57A, 'M', u'o'), + (0x1D57B, 'M', u'p'), + (0x1D57C, 'M', u'q'), + (0x1D57D, 'M', u'r'), + (0x1D57E, 'M', u's'), + (0x1D57F, 'M', u't'), + (0x1D580, 'M', u'u'), + (0x1D581, 'M', u'v'), + (0x1D582, 'M', u'w'), + (0x1D583, 'M', u'x'), + (0x1D584, 'M', u'y'), + (0x1D585, 'M', u'z'), + (0x1D586, 'M', u'a'), + (0x1D587, 'M', u'b'), + (0x1D588, 'M', u'c'), + (0x1D589, 'M', u'd'), + (0x1D58A, 'M', u'e'), + (0x1D58B, 'M', u'f'), + (0x1D58C, 'M', u'g'), + (0x1D58D, 'M', u'h'), + (0x1D58E, 'M', u'i'), + ] + +def _seg_62(): + return [ + (0x1D58F, 'M', u'j'), + (0x1D590, 'M', u'k'), + (0x1D591, 'M', u'l'), + (0x1D592, 'M', u'm'), + (0x1D593, 'M', u'n'), + (0x1D594, 'M', u'o'), + (0x1D595, 'M', u'p'), + (0x1D596, 'M', u'q'), + (0x1D597, 'M', u'r'), + (0x1D598, 'M', u's'), + (0x1D599, 'M', u't'), + (0x1D59A, 'M', u'u'), + (0x1D59B, 'M', u'v'), + (0x1D59C, 'M', u'w'), + (0x1D59D, 'M', u'x'), + (0x1D59E, 'M', u'y'), + (0x1D59F, 'M', u'z'), + (0x1D5A0, 'M', u'a'), + (0x1D5A1, 'M', u'b'), + (0x1D5A2, 'M', u'c'), + (0x1D5A3, 'M', u'd'), + (0x1D5A4, 'M', u'e'), + (0x1D5A5, 'M', u'f'), + (0x1D5A6, 'M', u'g'), + (0x1D5A7, 'M', u'h'), + (0x1D5A8, 'M', u'i'), + (0x1D5A9, 'M', u'j'), + (0x1D5AA, 'M', u'k'), + (0x1D5AB, 'M', u'l'), + (0x1D5AC, 'M', u'm'), + (0x1D5AD, 'M', u'n'), + (0x1D5AE, 'M', u'o'), + (0x1D5AF, 'M', u'p'), + (0x1D5B0, 'M', u'q'), + (0x1D5B1, 'M', u'r'), + (0x1D5B2, 'M', u's'), + (0x1D5B3, 'M', u't'), + (0x1D5B4, 'M', u'u'), + (0x1D5B5, 'M', u'v'), + (0x1D5B6, 'M', u'w'), + (0x1D5B7, 'M', u'x'), + (0x1D5B8, 'M', u'y'), + (0x1D5B9, 'M', u'z'), + (0x1D5BA, 'M', u'a'), + (0x1D5BB, 'M', u'b'), + (0x1D5BC, 'M', u'c'), + (0x1D5BD, 'M', u'd'), + (0x1D5BE, 'M', u'e'), + (0x1D5BF, 'M', u'f'), + (0x1D5C0, 'M', u'g'), + (0x1D5C1, 'M', u'h'), + (0x1D5C2, 'M', u'i'), + (0x1D5C3, 'M', u'j'), + (0x1D5C4, 'M', u'k'), + (0x1D5C5, 'M', u'l'), + (0x1D5C6, 'M', u'm'), + (0x1D5C7, 'M', u'n'), + (0x1D5C8, 'M', u'o'), + (0x1D5C9, 'M', u'p'), + (0x1D5CA, 'M', u'q'), + (0x1D5CB, 'M', u'r'), + (0x1D5CC, 'M', u's'), + (0x1D5CD, 'M', u't'), + (0x1D5CE, 'M', u'u'), + (0x1D5CF, 'M', u'v'), + (0x1D5D0, 'M', u'w'), + (0x1D5D1, 'M', u'x'), + (0x1D5D2, 'M', u'y'), + (0x1D5D3, 'M', u'z'), + (0x1D5D4, 'M', u'a'), + (0x1D5D5, 'M', u'b'), + (0x1D5D6, 'M', u'c'), + (0x1D5D7, 'M', u'd'), + (0x1D5D8, 'M', u'e'), + (0x1D5D9, 'M', u'f'), + (0x1D5DA, 'M', u'g'), + (0x1D5DB, 'M', u'h'), + (0x1D5DC, 'M', u'i'), + (0x1D5DD, 'M', u'j'), + (0x1D5DE, 'M', u'k'), + (0x1D5DF, 'M', u'l'), + (0x1D5E0, 'M', u'm'), + (0x1D5E1, 'M', u'n'), + (0x1D5E2, 'M', u'o'), + (0x1D5E3, 'M', u'p'), + (0x1D5E4, 'M', u'q'), + (0x1D5E5, 'M', u'r'), + (0x1D5E6, 'M', u's'), + (0x1D5E7, 'M', u't'), + (0x1D5E8, 'M', u'u'), + (0x1D5E9, 'M', u'v'), + (0x1D5EA, 'M', u'w'), + (0x1D5EB, 'M', u'x'), + (0x1D5EC, 'M', u'y'), + (0x1D5ED, 'M', u'z'), + (0x1D5EE, 'M', u'a'), + (0x1D5EF, 'M', u'b'), + (0x1D5F0, 'M', u'c'), + (0x1D5F1, 'M', u'd'), + (0x1D5F2, 'M', u'e'), + ] + +def _seg_63(): + return [ + (0x1D5F3, 'M', u'f'), + (0x1D5F4, 'M', u'g'), + (0x1D5F5, 'M', u'h'), + (0x1D5F6, 'M', u'i'), + (0x1D5F7, 'M', u'j'), + (0x1D5F8, 'M', u'k'), + (0x1D5F9, 'M', u'l'), + (0x1D5FA, 'M', u'm'), + (0x1D5FB, 'M', u'n'), + (0x1D5FC, 'M', u'o'), + (0x1D5FD, 'M', u'p'), + (0x1D5FE, 'M', u'q'), + (0x1D5FF, 'M', u'r'), + (0x1D600, 'M', u's'), + (0x1D601, 'M', u't'), + (0x1D602, 'M', u'u'), + (0x1D603, 'M', u'v'), + (0x1D604, 'M', u'w'), + (0x1D605, 'M', u'x'), + (0x1D606, 'M', u'y'), + (0x1D607, 'M', u'z'), + (0x1D608, 'M', u'a'), + (0x1D609, 'M', u'b'), + (0x1D60A, 'M', u'c'), + (0x1D60B, 'M', u'd'), + (0x1D60C, 'M', u'e'), + (0x1D60D, 'M', u'f'), + (0x1D60E, 'M', u'g'), + (0x1D60F, 'M', u'h'), + (0x1D610, 'M', u'i'), + (0x1D611, 'M', u'j'), + (0x1D612, 'M', u'k'), + (0x1D613, 'M', u'l'), + (0x1D614, 'M', u'm'), + (0x1D615, 'M', u'n'), + (0x1D616, 'M', u'o'), + (0x1D617, 'M', u'p'), + (0x1D618, 'M', u'q'), + (0x1D619, 'M', u'r'), + (0x1D61A, 'M', u's'), + (0x1D61B, 'M', u't'), + (0x1D61C, 'M', u'u'), + (0x1D61D, 'M', u'v'), + (0x1D61E, 'M', u'w'), + (0x1D61F, 'M', u'x'), + (0x1D620, 'M', u'y'), + (0x1D621, 'M', u'z'), + (0x1D622, 'M', u'a'), + (0x1D623, 'M', u'b'), + (0x1D624, 'M', u'c'), + (0x1D625, 'M', u'd'), + (0x1D626, 'M', u'e'), + (0x1D627, 'M', u'f'), + (0x1D628, 'M', u'g'), + (0x1D629, 'M', u'h'), + (0x1D62A, 'M', u'i'), + (0x1D62B, 'M', u'j'), + (0x1D62C, 'M', u'k'), + (0x1D62D, 'M', u'l'), + (0x1D62E, 'M', u'm'), + (0x1D62F, 'M', u'n'), + (0x1D630, 'M', u'o'), + (0x1D631, 'M', u'p'), + (0x1D632, 'M', u'q'), + (0x1D633, 'M', u'r'), + (0x1D634, 'M', u's'), + (0x1D635, 'M', u't'), + (0x1D636, 'M', u'u'), + (0x1D637, 'M', u'v'), + (0x1D638, 'M', u'w'), + (0x1D639, 'M', u'x'), + (0x1D63A, 'M', u'y'), + (0x1D63B, 'M', u'z'), + (0x1D63C, 'M', u'a'), + (0x1D63D, 'M', u'b'), + (0x1D63E, 'M', u'c'), + (0x1D63F, 'M', u'd'), + (0x1D640, 'M', u'e'), + (0x1D641, 'M', u'f'), + (0x1D642, 'M', u'g'), + (0x1D643, 'M', u'h'), + (0x1D644, 'M', u'i'), + (0x1D645, 'M', u'j'), + (0x1D646, 'M', u'k'), + (0x1D647, 'M', u'l'), + (0x1D648, 'M', u'm'), + (0x1D649, 'M', u'n'), + (0x1D64A, 'M', u'o'), + (0x1D64B, 'M', u'p'), + (0x1D64C, 'M', u'q'), + (0x1D64D, 'M', u'r'), + (0x1D64E, 'M', u's'), + (0x1D64F, 'M', u't'), + (0x1D650, 'M', u'u'), + (0x1D651, 'M', u'v'), + (0x1D652, 'M', u'w'), + (0x1D653, 'M', u'x'), + (0x1D654, 'M', u'y'), + (0x1D655, 'M', u'z'), + (0x1D656, 'M', u'a'), + ] + +def _seg_64(): + return [ + (0x1D657, 'M', u'b'), + (0x1D658, 'M', u'c'), + (0x1D659, 'M', u'd'), + (0x1D65A, 'M', u'e'), + (0x1D65B, 'M', u'f'), + (0x1D65C, 'M', u'g'), + (0x1D65D, 'M', u'h'), + (0x1D65E, 'M', u'i'), + (0x1D65F, 'M', u'j'), + (0x1D660, 'M', u'k'), + (0x1D661, 'M', u'l'), + (0x1D662, 'M', u'm'), + (0x1D663, 'M', u'n'), + (0x1D664, 'M', u'o'), + (0x1D665, 'M', u'p'), + (0x1D666, 'M', u'q'), + (0x1D667, 'M', u'r'), + (0x1D668, 'M', u's'), + (0x1D669, 'M', u't'), + (0x1D66A, 'M', u'u'), + (0x1D66B, 'M', u'v'), + (0x1D66C, 'M', u'w'), + (0x1D66D, 'M', u'x'), + (0x1D66E, 'M', u'y'), + (0x1D66F, 'M', u'z'), + (0x1D670, 'M', u'a'), + (0x1D671, 'M', u'b'), + (0x1D672, 'M', u'c'), + (0x1D673, 'M', u'd'), + (0x1D674, 'M', u'e'), + (0x1D675, 'M', u'f'), + (0x1D676, 'M', u'g'), + (0x1D677, 'M', u'h'), + (0x1D678, 'M', u'i'), + (0x1D679, 'M', u'j'), + (0x1D67A, 'M', u'k'), + (0x1D67B, 'M', u'l'), + (0x1D67C, 'M', u'm'), + (0x1D67D, 'M', u'n'), + (0x1D67E, 'M', u'o'), + (0x1D67F, 'M', u'p'), + (0x1D680, 'M', u'q'), + (0x1D681, 'M', u'r'), + (0x1D682, 'M', u's'), + (0x1D683, 'M', u't'), + (0x1D684, 'M', u'u'), + (0x1D685, 'M', u'v'), + (0x1D686, 'M', u'w'), + (0x1D687, 'M', u'x'), + (0x1D688, 'M', u'y'), + (0x1D689, 'M', u'z'), + (0x1D68A, 'M', u'a'), + (0x1D68B, 'M', u'b'), + (0x1D68C, 'M', u'c'), + (0x1D68D, 'M', u'd'), + (0x1D68E, 'M', u'e'), + (0x1D68F, 'M', u'f'), + (0x1D690, 'M', u'g'), + (0x1D691, 'M', u'h'), + (0x1D692, 'M', u'i'), + (0x1D693, 'M', u'j'), + (0x1D694, 'M', u'k'), + (0x1D695, 'M', u'l'), + (0x1D696, 'M', u'm'), + (0x1D697, 'M', u'n'), + (0x1D698, 'M', u'o'), + (0x1D699, 'M', u'p'), + (0x1D69A, 'M', u'q'), + (0x1D69B, 'M', u'r'), + (0x1D69C, 'M', u's'), + (0x1D69D, 'M', u't'), + (0x1D69E, 'M', u'u'), + (0x1D69F, 'M', u'v'), + (0x1D6A0, 'M', u'w'), + (0x1D6A1, 'M', u'x'), + (0x1D6A2, 'M', u'y'), + (0x1D6A3, 'M', u'z'), + (0x1D6A4, 'M', u'ı'), + (0x1D6A5, 'M', u'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', u'α'), + (0x1D6A9, 'M', u'β'), + (0x1D6AA, 'M', u'γ'), + (0x1D6AB, 'M', u'δ'), + (0x1D6AC, 'M', u'ε'), + (0x1D6AD, 'M', u'ζ'), + (0x1D6AE, 'M', u'η'), + (0x1D6AF, 'M', u'θ'), + (0x1D6B0, 'M', u'ι'), + (0x1D6B1, 'M', u'κ'), + (0x1D6B2, 'M', u'λ'), + (0x1D6B3, 'M', u'μ'), + (0x1D6B4, 'M', u'ν'), + (0x1D6B5, 'M', u'ξ'), + (0x1D6B6, 'M', u'ο'), + (0x1D6B7, 'M', u'π'), + (0x1D6B8, 'M', u'ρ'), + (0x1D6B9, 'M', u'θ'), + (0x1D6BA, 'M', u'σ'), + (0x1D6BB, 'M', u'τ'), + ] + +def _seg_65(): + return [ + (0x1D6BC, 'M', u'υ'), + (0x1D6BD, 'M', u'φ'), + (0x1D6BE, 'M', u'χ'), + (0x1D6BF, 'M', u'ψ'), + (0x1D6C0, 'M', u'ω'), + (0x1D6C1, 'M', u'∇'), + (0x1D6C2, 'M', u'α'), + (0x1D6C3, 'M', u'β'), + (0x1D6C4, 'M', u'γ'), + (0x1D6C5, 'M', u'δ'), + (0x1D6C6, 'M', u'ε'), + (0x1D6C7, 'M', u'ζ'), + (0x1D6C8, 'M', u'η'), + (0x1D6C9, 'M', u'θ'), + (0x1D6CA, 'M', u'ι'), + (0x1D6CB, 'M', u'κ'), + (0x1D6CC, 'M', u'λ'), + (0x1D6CD, 'M', u'μ'), + (0x1D6CE, 'M', u'ν'), + (0x1D6CF, 'M', u'ξ'), + (0x1D6D0, 'M', u'ο'), + (0x1D6D1, 'M', u'π'), + (0x1D6D2, 'M', u'ρ'), + (0x1D6D3, 'M', u'σ'), + (0x1D6D5, 'M', u'τ'), + (0x1D6D6, 'M', u'υ'), + (0x1D6D7, 'M', u'φ'), + (0x1D6D8, 'M', u'χ'), + (0x1D6D9, 'M', u'ψ'), + (0x1D6DA, 'M', u'ω'), + (0x1D6DB, 'M', u'∂'), + (0x1D6DC, 'M', u'ε'), + (0x1D6DD, 'M', u'θ'), + (0x1D6DE, 'M', u'κ'), + (0x1D6DF, 'M', u'φ'), + (0x1D6E0, 'M', u'ρ'), + (0x1D6E1, 'M', u'π'), + (0x1D6E2, 'M', u'α'), + (0x1D6E3, 'M', u'β'), + (0x1D6E4, 'M', u'γ'), + (0x1D6E5, 'M', u'δ'), + (0x1D6E6, 'M', u'ε'), + (0x1D6E7, 'M', u'ζ'), + (0x1D6E8, 'M', u'η'), + (0x1D6E9, 'M', u'θ'), + (0x1D6EA, 'M', u'ι'), + (0x1D6EB, 'M', u'κ'), + (0x1D6EC, 'M', u'λ'), + (0x1D6ED, 'M', u'μ'), + (0x1D6EE, 'M', u'ν'), + (0x1D6EF, 'M', u'ξ'), + (0x1D6F0, 'M', u'ο'), + (0x1D6F1, 'M', u'π'), + (0x1D6F2, 'M', u'ρ'), + (0x1D6F3, 'M', u'θ'), + (0x1D6F4, 'M', u'σ'), + (0x1D6F5, 'M', u'τ'), + (0x1D6F6, 'M', u'υ'), + (0x1D6F7, 'M', u'φ'), + (0x1D6F8, 'M', u'χ'), + (0x1D6F9, 'M', u'ψ'), + (0x1D6FA, 'M', u'ω'), + (0x1D6FB, 'M', u'∇'), + (0x1D6FC, 'M', u'α'), + (0x1D6FD, 'M', u'β'), + (0x1D6FE, 'M', u'γ'), + (0x1D6FF, 'M', u'δ'), + (0x1D700, 'M', u'ε'), + (0x1D701, 'M', u'ζ'), + (0x1D702, 'M', u'η'), + (0x1D703, 'M', u'θ'), + (0x1D704, 'M', u'ι'), + (0x1D705, 'M', u'κ'), + (0x1D706, 'M', u'λ'), + (0x1D707, 'M', u'μ'), + (0x1D708, 'M', u'ν'), + (0x1D709, 'M', u'ξ'), + (0x1D70A, 'M', u'ο'), + (0x1D70B, 'M', u'π'), + (0x1D70C, 'M', u'ρ'), + (0x1D70D, 'M', u'σ'), + (0x1D70F, 'M', u'τ'), + (0x1D710, 'M', u'υ'), + (0x1D711, 'M', u'φ'), + (0x1D712, 'M', u'χ'), + (0x1D713, 'M', u'ψ'), + (0x1D714, 'M', u'ω'), + (0x1D715, 'M', u'∂'), + (0x1D716, 'M', u'ε'), + (0x1D717, 'M', u'θ'), + (0x1D718, 'M', u'κ'), + (0x1D719, 'M', u'φ'), + (0x1D71A, 'M', u'ρ'), + (0x1D71B, 'M', u'π'), + (0x1D71C, 'M', u'α'), + (0x1D71D, 'M', u'β'), + (0x1D71E, 'M', u'γ'), + (0x1D71F, 'M', u'δ'), + (0x1D720, 'M', u'ε'), + (0x1D721, 'M', u'ζ'), + ] + +def _seg_66(): + return [ + (0x1D722, 'M', u'η'), + (0x1D723, 'M', u'θ'), + (0x1D724, 'M', u'ι'), + (0x1D725, 'M', u'κ'), + (0x1D726, 'M', u'λ'), + (0x1D727, 'M', u'μ'), + (0x1D728, 'M', u'ν'), + (0x1D729, 'M', u'ξ'), + (0x1D72A, 'M', u'ο'), + (0x1D72B, 'M', u'π'), + (0x1D72C, 'M', u'ρ'), + (0x1D72D, 'M', u'θ'), + (0x1D72E, 'M', u'σ'), + (0x1D72F, 'M', u'τ'), + (0x1D730, 'M', u'υ'), + (0x1D731, 'M', u'φ'), + (0x1D732, 'M', u'χ'), + (0x1D733, 'M', u'ψ'), + (0x1D734, 'M', u'ω'), + (0x1D735, 'M', u'∇'), + (0x1D736, 'M', u'α'), + (0x1D737, 'M', u'β'), + (0x1D738, 'M', u'γ'), + (0x1D739, 'M', u'δ'), + (0x1D73A, 'M', u'ε'), + (0x1D73B, 'M', u'ζ'), + (0x1D73C, 'M', u'η'), + (0x1D73D, 'M', u'θ'), + (0x1D73E, 'M', u'ι'), + (0x1D73F, 'M', u'κ'), + (0x1D740, 'M', u'λ'), + (0x1D741, 'M', u'μ'), + (0x1D742, 'M', u'ν'), + (0x1D743, 'M', u'ξ'), + (0x1D744, 'M', u'ο'), + (0x1D745, 'M', u'π'), + (0x1D746, 'M', u'ρ'), + (0x1D747, 'M', u'σ'), + (0x1D749, 'M', u'τ'), + (0x1D74A, 'M', u'υ'), + (0x1D74B, 'M', u'φ'), + (0x1D74C, 'M', u'χ'), + (0x1D74D, 'M', u'ψ'), + (0x1D74E, 'M', u'ω'), + (0x1D74F, 'M', u'∂'), + (0x1D750, 'M', u'ε'), + (0x1D751, 'M', u'θ'), + (0x1D752, 'M', u'κ'), + (0x1D753, 'M', u'φ'), + (0x1D754, 'M', u'ρ'), + (0x1D755, 'M', u'π'), + (0x1D756, 'M', u'α'), + (0x1D757, 'M', u'β'), + (0x1D758, 'M', u'γ'), + (0x1D759, 'M', u'δ'), + (0x1D75A, 'M', u'ε'), + (0x1D75B, 'M', u'ζ'), + (0x1D75C, 'M', u'η'), + (0x1D75D, 'M', u'θ'), + (0x1D75E, 'M', u'ι'), + (0x1D75F, 'M', u'κ'), + (0x1D760, 'M', u'λ'), + (0x1D761, 'M', u'μ'), + (0x1D762, 'M', u'ν'), + (0x1D763, 'M', u'ξ'), + (0x1D764, 'M', u'ο'), + (0x1D765, 'M', u'π'), + (0x1D766, 'M', u'ρ'), + (0x1D767, 'M', u'θ'), + (0x1D768, 'M', u'σ'), + (0x1D769, 'M', u'τ'), + (0x1D76A, 'M', u'υ'), + (0x1D76B, 'M', u'φ'), + (0x1D76C, 'M', u'χ'), + (0x1D76D, 'M', u'ψ'), + (0x1D76E, 'M', u'ω'), + (0x1D76F, 'M', u'∇'), + (0x1D770, 'M', u'α'), + (0x1D771, 'M', u'β'), + (0x1D772, 'M', u'γ'), + (0x1D773, 'M', u'δ'), + (0x1D774, 'M', u'ε'), + (0x1D775, 'M', u'ζ'), + (0x1D776, 'M', u'η'), + (0x1D777, 'M', u'θ'), + (0x1D778, 'M', u'ι'), + (0x1D779, 'M', u'κ'), + (0x1D77A, 'M', u'λ'), + (0x1D77B, 'M', u'μ'), + (0x1D77C, 'M', u'ν'), + (0x1D77D, 'M', u'ξ'), + (0x1D77E, 'M', u'ο'), + (0x1D77F, 'M', u'π'), + (0x1D780, 'M', u'ρ'), + (0x1D781, 'M', u'σ'), + (0x1D783, 'M', u'τ'), + (0x1D784, 'M', u'υ'), + (0x1D785, 'M', u'φ'), + (0x1D786, 'M', u'χ'), + (0x1D787, 'M', u'ψ'), + ] + +def _seg_67(): + return [ + (0x1D788, 'M', u'ω'), + (0x1D789, 'M', u'∂'), + (0x1D78A, 'M', u'ε'), + (0x1D78B, 'M', u'θ'), + (0x1D78C, 'M', u'κ'), + (0x1D78D, 'M', u'φ'), + (0x1D78E, 'M', u'ρ'), + (0x1D78F, 'M', u'π'), + (0x1D790, 'M', u'α'), + (0x1D791, 'M', u'β'), + (0x1D792, 'M', u'γ'), + (0x1D793, 'M', u'δ'), + (0x1D794, 'M', u'ε'), + (0x1D795, 'M', u'ζ'), + (0x1D796, 'M', u'η'), + (0x1D797, 'M', u'θ'), + (0x1D798, 'M', u'ι'), + (0x1D799, 'M', u'κ'), + (0x1D79A, 'M', u'λ'), + (0x1D79B, 'M', u'μ'), + (0x1D79C, 'M', u'ν'), + (0x1D79D, 'M', u'ξ'), + (0x1D79E, 'M', u'ο'), + (0x1D79F, 'M', u'π'), + (0x1D7A0, 'M', u'ρ'), + (0x1D7A1, 'M', u'θ'), + (0x1D7A2, 'M', u'σ'), + (0x1D7A3, 'M', u'τ'), + (0x1D7A4, 'M', u'υ'), + (0x1D7A5, 'M', u'φ'), + (0x1D7A6, 'M', u'χ'), + (0x1D7A7, 'M', u'ψ'), + (0x1D7A8, 'M', u'ω'), + (0x1D7A9, 'M', u'∇'), + (0x1D7AA, 'M', u'α'), + (0x1D7AB, 'M', u'β'), + (0x1D7AC, 'M', u'γ'), + (0x1D7AD, 'M', u'δ'), + (0x1D7AE, 'M', u'ε'), + (0x1D7AF, 'M', u'ζ'), + (0x1D7B0, 'M', u'η'), + (0x1D7B1, 'M', u'θ'), + (0x1D7B2, 'M', u'ι'), + (0x1D7B3, 'M', u'κ'), + (0x1D7B4, 'M', u'λ'), + (0x1D7B5, 'M', u'μ'), + (0x1D7B6, 'M', u'ν'), + (0x1D7B7, 'M', u'ξ'), + (0x1D7B8, 'M', u'ο'), + (0x1D7B9, 'M', u'π'), + (0x1D7BA, 'M', u'ρ'), + (0x1D7BB, 'M', u'σ'), + (0x1D7BD, 'M', u'τ'), + (0x1D7BE, 'M', u'υ'), + (0x1D7BF, 'M', u'φ'), + (0x1D7C0, 'M', u'χ'), + (0x1D7C1, 'M', u'ψ'), + (0x1D7C2, 'M', u'ω'), + (0x1D7C3, 'M', u'∂'), + (0x1D7C4, 'M', u'ε'), + (0x1D7C5, 'M', u'θ'), + (0x1D7C6, 'M', u'κ'), + (0x1D7C7, 'M', u'φ'), + (0x1D7C8, 'M', u'ρ'), + (0x1D7C9, 'M', u'π'), + (0x1D7CA, 'M', u'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', u'0'), + (0x1D7CF, 'M', u'1'), + (0x1D7D0, 'M', u'2'), + (0x1D7D1, 'M', u'3'), + (0x1D7D2, 'M', u'4'), + (0x1D7D3, 'M', u'5'), + (0x1D7D4, 'M', u'6'), + (0x1D7D5, 'M', u'7'), + (0x1D7D6, 'M', u'8'), + (0x1D7D7, 'M', u'9'), + (0x1D7D8, 'M', u'0'), + (0x1D7D9, 'M', u'1'), + (0x1D7DA, 'M', u'2'), + (0x1D7DB, 'M', u'3'), + (0x1D7DC, 'M', u'4'), + (0x1D7DD, 'M', u'5'), + (0x1D7DE, 'M', u'6'), + (0x1D7DF, 'M', u'7'), + (0x1D7E0, 'M', u'8'), + (0x1D7E1, 'M', u'9'), + (0x1D7E2, 'M', u'0'), + (0x1D7E3, 'M', u'1'), + (0x1D7E4, 'M', u'2'), + (0x1D7E5, 'M', u'3'), + (0x1D7E6, 'M', u'4'), + (0x1D7E7, 'M', u'5'), + (0x1D7E8, 'M', u'6'), + (0x1D7E9, 'M', u'7'), + (0x1D7EA, 'M', u'8'), + (0x1D7EB, 'M', u'9'), + (0x1D7EC, 'M', u'0'), + (0x1D7ED, 'M', u'1'), + (0x1D7EE, 'M', u'2'), + ] + +def _seg_68(): + return [ + (0x1D7EF, 'M', u'3'), + (0x1D7F0, 'M', u'4'), + (0x1D7F1, 'M', u'5'), + (0x1D7F2, 'M', u'6'), + (0x1D7F3, 'M', u'7'), + (0x1D7F4, 'M', u'8'), + (0x1D7F5, 'M', u'9'), + (0x1D7F6, 'M', u'0'), + (0x1D7F7, 'M', u'1'), + (0x1D7F8, 'M', u'2'), + (0x1D7F9, 'M', u'3'), + (0x1D7FA, 'M', u'4'), + (0x1D7FB, 'M', u'5'), + (0x1D7FC, 'M', u'6'), + (0x1D7FD, 'M', u'7'), + (0x1D7FE, 'M', u'8'), + (0x1D7FF, 'M', u'9'), + (0x1D800, 'V'), + (0x1DA8C, 'X'), + (0x1DA9B, 'V'), + (0x1DAA0, 'X'), + (0x1DAA1, 'V'), + (0x1DAB0, 'X'), + (0x1E000, 'V'), + (0x1E007, 'X'), + (0x1E008, 'V'), + (0x1E019, 'X'), + (0x1E01B, 'V'), + (0x1E022, 'X'), + (0x1E023, 'V'), + (0x1E025, 'X'), + (0x1E026, 'V'), + (0x1E02B, 'X'), + (0x1E800, 'V'), + (0x1E8C5, 'X'), + (0x1E8C7, 'V'), + (0x1E8D7, 'X'), + (0x1E900, 'M', u'𞤢'), + (0x1E901, 'M', u'𞤣'), + (0x1E902, 'M', u'𞤤'), + (0x1E903, 'M', u'𞤥'), + (0x1E904, 'M', u'𞤦'), + (0x1E905, 'M', u'𞤧'), + (0x1E906, 'M', u'𞤨'), + (0x1E907, 'M', u'𞤩'), + (0x1E908, 'M', u'𞤪'), + (0x1E909, 'M', u'𞤫'), + (0x1E90A, 'M', u'𞤬'), + (0x1E90B, 'M', u'𞤭'), + (0x1E90C, 'M', u'𞤮'), + (0x1E90D, 'M', u'𞤯'), + (0x1E90E, 'M', u'𞤰'), + (0x1E90F, 'M', u'𞤱'), + (0x1E910, 'M', u'𞤲'), + (0x1E911, 'M', u'𞤳'), + (0x1E912, 'M', u'𞤴'), + (0x1E913, 'M', u'𞤵'), + (0x1E914, 'M', u'𞤶'), + (0x1E915, 'M', u'𞤷'), + (0x1E916, 'M', u'𞤸'), + (0x1E917, 'M', u'𞤹'), + (0x1E918, 'M', u'𞤺'), + (0x1E919, 'M', u'𞤻'), + (0x1E91A, 'M', u'𞤼'), + (0x1E91B, 'M', u'𞤽'), + (0x1E91C, 'M', u'𞤾'), + (0x1E91D, 'M', u'𞤿'), + (0x1E91E, 'M', u'𞥀'), + (0x1E91F, 'M', u'𞥁'), + (0x1E920, 'M', u'𞥂'), + (0x1E921, 'M', u'𞥃'), + (0x1E922, 'V'), + (0x1E94B, 'X'), + (0x1E950, 'V'), + (0x1E95A, 'X'), + (0x1E95E, 'V'), + (0x1E960, 'X'), + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1EE00, 'M', u'ا'), + (0x1EE01, 'M', u'ب'), + (0x1EE02, 'M', u'ج'), + (0x1EE03, 'M', u'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', u'و'), + (0x1EE06, 'M', u'ز'), + (0x1EE07, 'M', u'ح'), + (0x1EE08, 'M', u'ط'), + (0x1EE09, 'M', u'ي'), + (0x1EE0A, 'M', u'ك'), + (0x1EE0B, 'M', u'ل'), + (0x1EE0C, 'M', u'م'), + (0x1EE0D, 'M', u'ن'), + (0x1EE0E, 'M', u'س'), + (0x1EE0F, 'M', u'ع'), + (0x1EE10, 'M', u'ف'), + (0x1EE11, 'M', u'ص'), + (0x1EE12, 'M', u'ق'), + (0x1EE13, 'M', u'ر'), + (0x1EE14, 'M', u'ش'), + ] + +def _seg_69(): + return [ + (0x1EE15, 'M', u'ت'), + (0x1EE16, 'M', u'ث'), + (0x1EE17, 'M', u'خ'), + (0x1EE18, 'M', u'ذ'), + (0x1EE19, 'M', u'ض'), + (0x1EE1A, 'M', u'ظ'), + (0x1EE1B, 'M', u'غ'), + (0x1EE1C, 'M', u'ٮ'), + (0x1EE1D, 'M', u'ں'), + (0x1EE1E, 'M', u'ڡ'), + (0x1EE1F, 'M', u'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', u'ب'), + (0x1EE22, 'M', u'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', u'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', u'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', u'ي'), + (0x1EE2A, 'M', u'ك'), + (0x1EE2B, 'M', u'ل'), + (0x1EE2C, 'M', u'م'), + (0x1EE2D, 'M', u'ن'), + (0x1EE2E, 'M', u'س'), + (0x1EE2F, 'M', u'ع'), + (0x1EE30, 'M', u'ف'), + (0x1EE31, 'M', u'ص'), + (0x1EE32, 'M', u'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', u'ش'), + (0x1EE35, 'M', u'ت'), + (0x1EE36, 'M', u'ث'), + (0x1EE37, 'M', u'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', u'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', u'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', u'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', u'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', u'ي'), + (0x1EE4A, 'X'), + (0x1EE4B, 'M', u'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', u'ن'), + (0x1EE4E, 'M', u'س'), + (0x1EE4F, 'M', u'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', u'ص'), + (0x1EE52, 'M', u'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', u'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', u'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', u'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', u'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', u'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', u'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', u'ب'), + (0x1EE62, 'M', u'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', u'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', u'ح'), + (0x1EE68, 'M', u'ط'), + (0x1EE69, 'M', u'ي'), + (0x1EE6A, 'M', u'ك'), + (0x1EE6B, 'X'), + (0x1EE6C, 'M', u'م'), + (0x1EE6D, 'M', u'ن'), + (0x1EE6E, 'M', u'س'), + (0x1EE6F, 'M', u'ع'), + (0x1EE70, 'M', u'ف'), + (0x1EE71, 'M', u'ص'), + (0x1EE72, 'M', u'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', u'ش'), + (0x1EE75, 'M', u'ت'), + (0x1EE76, 'M', u'ث'), + (0x1EE77, 'M', u'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', u'ض'), + (0x1EE7A, 'M', u'ظ'), + (0x1EE7B, 'M', u'غ'), + (0x1EE7C, 'M', u'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', u'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', u'ا'), + (0x1EE81, 'M', u'ب'), + (0x1EE82, 'M', u'ج'), + (0x1EE83, 'M', u'د'), + ] + +def _seg_70(): + return [ + (0x1EE84, 'M', u'ه'), + (0x1EE85, 'M', u'و'), + (0x1EE86, 'M', u'ز'), + (0x1EE87, 'M', u'ح'), + (0x1EE88, 'M', u'ط'), + (0x1EE89, 'M', u'ي'), + (0x1EE8A, 'X'), + (0x1EE8B, 'M', u'ل'), + (0x1EE8C, 'M', u'م'), + (0x1EE8D, 'M', u'ن'), + (0x1EE8E, 'M', u'س'), + (0x1EE8F, 'M', u'ع'), + (0x1EE90, 'M', u'ف'), + (0x1EE91, 'M', u'ص'), + (0x1EE92, 'M', u'ق'), + (0x1EE93, 'M', u'ر'), + (0x1EE94, 'M', u'ش'), + (0x1EE95, 'M', u'ت'), + (0x1EE96, 'M', u'ث'), + (0x1EE97, 'M', u'خ'), + (0x1EE98, 'M', u'ذ'), + (0x1EE99, 'M', u'ض'), + (0x1EE9A, 'M', u'ظ'), + (0x1EE9B, 'M', u'غ'), + (0x1EE9C, 'X'), + (0x1EEA1, 'M', u'ب'), + (0x1EEA2, 'M', u'ج'), + (0x1EEA3, 'M', u'د'), + (0x1EEA4, 'X'), + (0x1EEA5, 'M', u'و'), + (0x1EEA6, 'M', u'ز'), + (0x1EEA7, 'M', u'ح'), + (0x1EEA8, 'M', u'ط'), + (0x1EEA9, 'M', u'ي'), + (0x1EEAA, 'X'), + (0x1EEAB, 'M', u'ل'), + (0x1EEAC, 'M', u'م'), + (0x1EEAD, 'M', u'ن'), + (0x1EEAE, 'M', u'س'), + (0x1EEAF, 'M', u'ع'), + (0x1EEB0, 'M', u'ف'), + (0x1EEB1, 'M', u'ص'), + (0x1EEB2, 'M', u'ق'), + (0x1EEB3, 'M', u'ر'), + (0x1EEB4, 'M', u'ش'), + (0x1EEB5, 'M', u'ت'), + (0x1EEB6, 'M', u'ث'), + (0x1EEB7, 'M', u'خ'), + (0x1EEB8, 'M', u'ذ'), + (0x1EEB9, 'M', u'ض'), + (0x1EEBA, 'M', u'ظ'), + (0x1EEBB, 'M', u'غ'), + (0x1EEBC, 'X'), + (0x1EEF0, 'V'), + (0x1EEF2, 'X'), + (0x1F000, 'V'), + (0x1F02C, 'X'), + (0x1F030, 'V'), + (0x1F094, 'X'), + (0x1F0A0, 'V'), + (0x1F0AF, 'X'), + (0x1F0B1, 'V'), + (0x1F0C0, 'X'), + (0x1F0C1, 'V'), + (0x1F0D0, 'X'), + (0x1F0D1, 'V'), + (0x1F0F6, 'X'), + (0x1F101, '3', u'0,'), + (0x1F102, '3', u'1,'), + (0x1F103, '3', u'2,'), + (0x1F104, '3', u'3,'), + (0x1F105, '3', u'4,'), + (0x1F106, '3', u'5,'), + (0x1F107, '3', u'6,'), + (0x1F108, '3', u'7,'), + (0x1F109, '3', u'8,'), + (0x1F10A, '3', u'9,'), + (0x1F10B, 'V'), + (0x1F10D, 'X'), + (0x1F110, '3', u'(a)'), + (0x1F111, '3', u'(b)'), + (0x1F112, '3', u'(c)'), + (0x1F113, '3', u'(d)'), + (0x1F114, '3', u'(e)'), + (0x1F115, '3', u'(f)'), + (0x1F116, '3', u'(g)'), + (0x1F117, '3', u'(h)'), + (0x1F118, '3', u'(i)'), + (0x1F119, '3', u'(j)'), + (0x1F11A, '3', u'(k)'), + (0x1F11B, '3', u'(l)'), + (0x1F11C, '3', u'(m)'), + (0x1F11D, '3', u'(n)'), + (0x1F11E, '3', u'(o)'), + (0x1F11F, '3', u'(p)'), + (0x1F120, '3', u'(q)'), + (0x1F121, '3', u'(r)'), + (0x1F122, '3', u'(s)'), + (0x1F123, '3', u'(t)'), + (0x1F124, '3', u'(u)'), + ] + +def _seg_71(): + return [ + (0x1F125, '3', u'(v)'), + (0x1F126, '3', u'(w)'), + (0x1F127, '3', u'(x)'), + (0x1F128, '3', u'(y)'), + (0x1F129, '3', u'(z)'), + (0x1F12A, 'M', u'〔s〕'), + (0x1F12B, 'M', u'c'), + (0x1F12C, 'M', u'r'), + (0x1F12D, 'M', u'cd'), + (0x1F12E, 'M', u'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', u'a'), + (0x1F131, 'M', u'b'), + (0x1F132, 'M', u'c'), + (0x1F133, 'M', u'd'), + (0x1F134, 'M', u'e'), + (0x1F135, 'M', u'f'), + (0x1F136, 'M', u'g'), + (0x1F137, 'M', u'h'), + (0x1F138, 'M', u'i'), + (0x1F139, 'M', u'j'), + (0x1F13A, 'M', u'k'), + (0x1F13B, 'M', u'l'), + (0x1F13C, 'M', u'm'), + (0x1F13D, 'M', u'n'), + (0x1F13E, 'M', u'o'), + (0x1F13F, 'M', u'p'), + (0x1F140, 'M', u'q'), + (0x1F141, 'M', u'r'), + (0x1F142, 'M', u's'), + (0x1F143, 'M', u't'), + (0x1F144, 'M', u'u'), + (0x1F145, 'M', u'v'), + (0x1F146, 'M', u'w'), + (0x1F147, 'M', u'x'), + (0x1F148, 'M', u'y'), + (0x1F149, 'M', u'z'), + (0x1F14A, 'M', u'hv'), + (0x1F14B, 'M', u'mv'), + (0x1F14C, 'M', u'sd'), + (0x1F14D, 'M', u'ss'), + (0x1F14E, 'M', u'ppv'), + (0x1F14F, 'M', u'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', u'mc'), + (0x1F16B, 'M', u'md'), + (0x1F16C, 'X'), + (0x1F170, 'V'), + (0x1F190, 'M', u'dj'), + (0x1F191, 'V'), + (0x1F1AD, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', u'ほか'), + (0x1F201, 'M', u'ココ'), + (0x1F202, 'M', u'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', u'手'), + (0x1F211, 'M', u'字'), + (0x1F212, 'M', u'双'), + (0x1F213, 'M', u'デ'), + (0x1F214, 'M', u'二'), + (0x1F215, 'M', u'多'), + (0x1F216, 'M', u'解'), + (0x1F217, 'M', u'天'), + (0x1F218, 'M', u'交'), + (0x1F219, 'M', u'映'), + (0x1F21A, 'M', u'無'), + (0x1F21B, 'M', u'料'), + (0x1F21C, 'M', u'前'), + (0x1F21D, 'M', u'後'), + (0x1F21E, 'M', u'再'), + (0x1F21F, 'M', u'新'), + (0x1F220, 'M', u'初'), + (0x1F221, 'M', u'終'), + (0x1F222, 'M', u'生'), + (0x1F223, 'M', u'販'), + (0x1F224, 'M', u'声'), + (0x1F225, 'M', u'吹'), + (0x1F226, 'M', u'演'), + (0x1F227, 'M', u'投'), + (0x1F228, 'M', u'捕'), + (0x1F229, 'M', u'一'), + (0x1F22A, 'M', u'三'), + (0x1F22B, 'M', u'遊'), + (0x1F22C, 'M', u'左'), + (0x1F22D, 'M', u'中'), + (0x1F22E, 'M', u'右'), + (0x1F22F, 'M', u'指'), + (0x1F230, 'M', u'走'), + (0x1F231, 'M', u'打'), + (0x1F232, 'M', u'禁'), + (0x1F233, 'M', u'空'), + (0x1F234, 'M', u'合'), + (0x1F235, 'M', u'満'), + (0x1F236, 'M', u'有'), + (0x1F237, 'M', u'月'), + (0x1F238, 'M', u'申'), + (0x1F239, 'M', u'割'), + (0x1F23A, 'M', u'営'), + (0x1F23B, 'M', u'配'), + ] + +def _seg_72(): + return [ + (0x1F23C, 'X'), + (0x1F240, 'M', u'〔本〕'), + (0x1F241, 'M', u'〔三〕'), + (0x1F242, 'M', u'〔二〕'), + (0x1F243, 'M', u'〔安〕'), + (0x1F244, 'M', u'〔点〕'), + (0x1F245, 'M', u'〔打〕'), + (0x1F246, 'M', u'〔盗〕'), + (0x1F247, 'M', u'〔勝〕'), + (0x1F248, 'M', u'〔敗〕'), + (0x1F249, 'X'), + (0x1F250, 'M', u'得'), + (0x1F251, 'M', u'可'), + (0x1F252, 'X'), + (0x1F260, 'V'), + (0x1F266, 'X'), + (0x1F300, 'V'), + (0x1F6D5, 'X'), + (0x1F6E0, 'V'), + (0x1F6ED, 'X'), + (0x1F6F0, 'V'), + (0x1F6FA, 'X'), + (0x1F700, 'V'), + (0x1F774, 'X'), + (0x1F780, 'V'), + (0x1F7D9, 'X'), + (0x1F800, 'V'), + (0x1F80C, 'X'), + (0x1F810, 'V'), + (0x1F848, 'X'), + (0x1F850, 'V'), + (0x1F85A, 'X'), + (0x1F860, 'V'), + (0x1F888, 'X'), + (0x1F890, 'V'), + (0x1F8AE, 'X'), + (0x1F900, 'V'), + (0x1F90C, 'X'), + (0x1F910, 'V'), + (0x1F93F, 'X'), + (0x1F940, 'V'), + (0x1F971, 'X'), + (0x1F973, 'V'), + (0x1F977, 'X'), + (0x1F97A, 'V'), + (0x1F97B, 'X'), + (0x1F97C, 'V'), + (0x1F9A3, 'X'), + (0x1F9B0, 'V'), + (0x1F9BA, 'X'), + (0x1F9C0, 'V'), + (0x1F9C3, 'X'), + (0x1F9D0, 'V'), + (0x1FA00, 'X'), + (0x1FA60, 'V'), + (0x1FA6E, 'X'), + (0x20000, 'V'), + (0x2A6D7, 'X'), + (0x2A700, 'V'), + (0x2B735, 'X'), + (0x2B740, 'V'), + (0x2B81E, 'X'), + (0x2B820, 'V'), + (0x2CEA2, 'X'), + (0x2CEB0, 'V'), + (0x2EBE1, 'X'), + (0x2F800, 'M', u'丽'), + (0x2F801, 'M', u'丸'), + (0x2F802, 'M', u'乁'), + (0x2F803, 'M', u'𠄢'), + (0x2F804, 'M', u'你'), + (0x2F805, 'M', u'侮'), + (0x2F806, 'M', u'侻'), + (0x2F807, 'M', u'倂'), + (0x2F808, 'M', u'偺'), + (0x2F809, 'M', u'備'), + (0x2F80A, 'M', u'僧'), + (0x2F80B, 'M', u'像'), + (0x2F80C, 'M', u'㒞'), + (0x2F80D, 'M', u'𠘺'), + (0x2F80E, 'M', u'免'), + (0x2F80F, 'M', u'兔'), + (0x2F810, 'M', u'兤'), + (0x2F811, 'M', u'具'), + (0x2F812, 'M', u'𠔜'), + (0x2F813, 'M', u'㒹'), + (0x2F814, 'M', u'內'), + (0x2F815, 'M', u'再'), + (0x2F816, 'M', u'𠕋'), + (0x2F817, 'M', u'冗'), + (0x2F818, 'M', u'冤'), + (0x2F819, 'M', u'仌'), + (0x2F81A, 'M', u'冬'), + (0x2F81B, 'M', u'况'), + (0x2F81C, 'M', u'𩇟'), + (0x2F81D, 'M', u'凵'), + (0x2F81E, 'M', u'刃'), + (0x2F81F, 'M', u'㓟'), + (0x2F820, 'M', u'刻'), + (0x2F821, 'M', u'剆'), + ] + +def _seg_73(): + return [ + (0x2F822, 'M', u'割'), + (0x2F823, 'M', u'剷'), + (0x2F824, 'M', u'㔕'), + (0x2F825, 'M', u'勇'), + (0x2F826, 'M', u'勉'), + (0x2F827, 'M', u'勤'), + (0x2F828, 'M', u'勺'), + (0x2F829, 'M', u'包'), + (0x2F82A, 'M', u'匆'), + (0x2F82B, 'M', u'北'), + (0x2F82C, 'M', u'卉'), + (0x2F82D, 'M', u'卑'), + (0x2F82E, 'M', u'博'), + (0x2F82F, 'M', u'即'), + (0x2F830, 'M', u'卽'), + (0x2F831, 'M', u'卿'), + (0x2F834, 'M', u'𠨬'), + (0x2F835, 'M', u'灰'), + (0x2F836, 'M', u'及'), + (0x2F837, 'M', u'叟'), + (0x2F838, 'M', u'𠭣'), + (0x2F839, 'M', u'叫'), + (0x2F83A, 'M', u'叱'), + (0x2F83B, 'M', u'吆'), + (0x2F83C, 'M', u'咞'), + (0x2F83D, 'M', u'吸'), + (0x2F83E, 'M', u'呈'), + (0x2F83F, 'M', u'周'), + (0x2F840, 'M', u'咢'), + (0x2F841, 'M', u'哶'), + (0x2F842, 'M', u'唐'), + (0x2F843, 'M', u'啓'), + (0x2F844, 'M', u'啣'), + (0x2F845, 'M', u'善'), + (0x2F847, 'M', u'喙'), + (0x2F848, 'M', u'喫'), + (0x2F849, 'M', u'喳'), + (0x2F84A, 'M', u'嗂'), + (0x2F84B, 'M', u'圖'), + (0x2F84C, 'M', u'嘆'), + (0x2F84D, 'M', u'圗'), + (0x2F84E, 'M', u'噑'), + (0x2F84F, 'M', u'噴'), + (0x2F850, 'M', u'切'), + (0x2F851, 'M', u'壮'), + (0x2F852, 'M', u'城'), + (0x2F853, 'M', u'埴'), + (0x2F854, 'M', u'堍'), + (0x2F855, 'M', u'型'), + (0x2F856, 'M', u'堲'), + (0x2F857, 'M', u'報'), + (0x2F858, 'M', u'墬'), + (0x2F859, 'M', u'𡓤'), + (0x2F85A, 'M', u'売'), + (0x2F85B, 'M', u'壷'), + (0x2F85C, 'M', u'夆'), + (0x2F85D, 'M', u'多'), + (0x2F85E, 'M', u'夢'), + (0x2F85F, 'M', u'奢'), + (0x2F860, 'M', u'𡚨'), + (0x2F861, 'M', u'𡛪'), + (0x2F862, 'M', u'姬'), + (0x2F863, 'M', u'娛'), + (0x2F864, 'M', u'娧'), + (0x2F865, 'M', u'姘'), + (0x2F866, 'M', u'婦'), + (0x2F867, 'M', u'㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', u'嬈'), + (0x2F86A, 'M', u'嬾'), + (0x2F86C, 'M', u'𡧈'), + (0x2F86D, 'M', u'寃'), + (0x2F86E, 'M', u'寘'), + (0x2F86F, 'M', u'寧'), + (0x2F870, 'M', u'寳'), + (0x2F871, 'M', u'𡬘'), + (0x2F872, 'M', u'寿'), + (0x2F873, 'M', u'将'), + (0x2F874, 'X'), + (0x2F875, 'M', u'尢'), + (0x2F876, 'M', u'㞁'), + (0x2F877, 'M', u'屠'), + (0x2F878, 'M', u'屮'), + (0x2F879, 'M', u'峀'), + (0x2F87A, 'M', u'岍'), + (0x2F87B, 'M', u'𡷤'), + (0x2F87C, 'M', u'嵃'), + (0x2F87D, 'M', u'𡷦'), + (0x2F87E, 'M', u'嵮'), + (0x2F87F, 'M', u'嵫'), + (0x2F880, 'M', u'嵼'), + (0x2F881, 'M', u'巡'), + (0x2F882, 'M', u'巢'), + (0x2F883, 'M', u'㠯'), + (0x2F884, 'M', u'巽'), + (0x2F885, 'M', u'帨'), + (0x2F886, 'M', u'帽'), + (0x2F887, 'M', u'幩'), + (0x2F888, 'M', u'㡢'), + (0x2F889, 'M', u'𢆃'), + ] + +def _seg_74(): + return [ + (0x2F88A, 'M', u'㡼'), + (0x2F88B, 'M', u'庰'), + (0x2F88C, 'M', u'庳'), + (0x2F88D, 'M', u'庶'), + (0x2F88E, 'M', u'廊'), + (0x2F88F, 'M', u'𪎒'), + (0x2F890, 'M', u'廾'), + (0x2F891, 'M', u'𢌱'), + (0x2F893, 'M', u'舁'), + (0x2F894, 'M', u'弢'), + (0x2F896, 'M', u'㣇'), + (0x2F897, 'M', u'𣊸'), + (0x2F898, 'M', u'𦇚'), + (0x2F899, 'M', u'形'), + (0x2F89A, 'M', u'彫'), + (0x2F89B, 'M', u'㣣'), + (0x2F89C, 'M', u'徚'), + (0x2F89D, 'M', u'忍'), + (0x2F89E, 'M', u'志'), + (0x2F89F, 'M', u'忹'), + (0x2F8A0, 'M', u'悁'), + (0x2F8A1, 'M', u'㤺'), + (0x2F8A2, 'M', u'㤜'), + (0x2F8A3, 'M', u'悔'), + (0x2F8A4, 'M', u'𢛔'), + (0x2F8A5, 'M', u'惇'), + (0x2F8A6, 'M', u'慈'), + (0x2F8A7, 'M', u'慌'), + (0x2F8A8, 'M', u'慎'), + (0x2F8A9, 'M', u'慌'), + (0x2F8AA, 'M', u'慺'), + (0x2F8AB, 'M', u'憎'), + (0x2F8AC, 'M', u'憲'), + (0x2F8AD, 'M', u'憤'), + (0x2F8AE, 'M', u'憯'), + (0x2F8AF, 'M', u'懞'), + (0x2F8B0, 'M', u'懲'), + (0x2F8B1, 'M', u'懶'), + (0x2F8B2, 'M', u'成'), + (0x2F8B3, 'M', u'戛'), + (0x2F8B4, 'M', u'扝'), + (0x2F8B5, 'M', u'抱'), + (0x2F8B6, 'M', u'拔'), + (0x2F8B7, 'M', u'捐'), + (0x2F8B8, 'M', u'𢬌'), + (0x2F8B9, 'M', u'挽'), + (0x2F8BA, 'M', u'拼'), + (0x2F8BB, 'M', u'捨'), + (0x2F8BC, 'M', u'掃'), + (0x2F8BD, 'M', u'揤'), + (0x2F8BE, 'M', u'𢯱'), + (0x2F8BF, 'M', u'搢'), + (0x2F8C0, 'M', u'揅'), + (0x2F8C1, 'M', u'掩'), + (0x2F8C2, 'M', u'㨮'), + (0x2F8C3, 'M', u'摩'), + (0x2F8C4, 'M', u'摾'), + (0x2F8C5, 'M', u'撝'), + (0x2F8C6, 'M', u'摷'), + (0x2F8C7, 'M', u'㩬'), + (0x2F8C8, 'M', u'敏'), + (0x2F8C9, 'M', u'敬'), + (0x2F8CA, 'M', u'𣀊'), + (0x2F8CB, 'M', u'旣'), + (0x2F8CC, 'M', u'書'), + (0x2F8CD, 'M', u'晉'), + (0x2F8CE, 'M', u'㬙'), + (0x2F8CF, 'M', u'暑'), + (0x2F8D0, 'M', u'㬈'), + (0x2F8D1, 'M', u'㫤'), + (0x2F8D2, 'M', u'冒'), + (0x2F8D3, 'M', u'冕'), + (0x2F8D4, 'M', u'最'), + (0x2F8D5, 'M', u'暜'), + (0x2F8D6, 'M', u'肭'), + (0x2F8D7, 'M', u'䏙'), + (0x2F8D8, 'M', u'朗'), + (0x2F8D9, 'M', u'望'), + (0x2F8DA, 'M', u'朡'), + (0x2F8DB, 'M', u'杞'), + (0x2F8DC, 'M', u'杓'), + (0x2F8DD, 'M', u'𣏃'), + (0x2F8DE, 'M', u'㭉'), + (0x2F8DF, 'M', u'柺'), + (0x2F8E0, 'M', u'枅'), + (0x2F8E1, 'M', u'桒'), + (0x2F8E2, 'M', u'梅'), + (0x2F8E3, 'M', u'𣑭'), + (0x2F8E4, 'M', u'梎'), + (0x2F8E5, 'M', u'栟'), + (0x2F8E6, 'M', u'椔'), + (0x2F8E7, 'M', u'㮝'), + (0x2F8E8, 'M', u'楂'), + (0x2F8E9, 'M', u'榣'), + (0x2F8EA, 'M', u'槪'), + (0x2F8EB, 'M', u'檨'), + (0x2F8EC, 'M', u'𣚣'), + (0x2F8ED, 'M', u'櫛'), + (0x2F8EE, 'M', u'㰘'), + (0x2F8EF, 'M', u'次'), + ] + +def _seg_75(): + return [ + (0x2F8F0, 'M', u'𣢧'), + (0x2F8F1, 'M', u'歔'), + (0x2F8F2, 'M', u'㱎'), + (0x2F8F3, 'M', u'歲'), + (0x2F8F4, 'M', u'殟'), + (0x2F8F5, 'M', u'殺'), + (0x2F8F6, 'M', u'殻'), + (0x2F8F7, 'M', u'𣪍'), + (0x2F8F8, 'M', u'𡴋'), + (0x2F8F9, 'M', u'𣫺'), + (0x2F8FA, 'M', u'汎'), + (0x2F8FB, 'M', u'𣲼'), + (0x2F8FC, 'M', u'沿'), + (0x2F8FD, 'M', u'泍'), + (0x2F8FE, 'M', u'汧'), + (0x2F8FF, 'M', u'洖'), + (0x2F900, 'M', u'派'), + (0x2F901, 'M', u'海'), + (0x2F902, 'M', u'流'), + (0x2F903, 'M', u'浩'), + (0x2F904, 'M', u'浸'), + (0x2F905, 'M', u'涅'), + (0x2F906, 'M', u'𣴞'), + (0x2F907, 'M', u'洴'), + (0x2F908, 'M', u'港'), + (0x2F909, 'M', u'湮'), + (0x2F90A, 'M', u'㴳'), + (0x2F90B, 'M', u'滋'), + (0x2F90C, 'M', u'滇'), + (0x2F90D, 'M', u'𣻑'), + (0x2F90E, 'M', u'淹'), + (0x2F90F, 'M', u'潮'), + (0x2F910, 'M', u'𣽞'), + (0x2F911, 'M', u'𣾎'), + (0x2F912, 'M', u'濆'), + (0x2F913, 'M', u'瀹'), + (0x2F914, 'M', u'瀞'), + (0x2F915, 'M', u'瀛'), + (0x2F916, 'M', u'㶖'), + (0x2F917, 'M', u'灊'), + (0x2F918, 'M', u'災'), + (0x2F919, 'M', u'灷'), + (0x2F91A, 'M', u'炭'), + (0x2F91B, 'M', u'𠔥'), + (0x2F91C, 'M', u'煅'), + (0x2F91D, 'M', u'𤉣'), + (0x2F91E, 'M', u'熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', u'爨'), + (0x2F921, 'M', u'爵'), + (0x2F922, 'M', u'牐'), + (0x2F923, 'M', u'𤘈'), + (0x2F924, 'M', u'犀'), + (0x2F925, 'M', u'犕'), + (0x2F926, 'M', u'𤜵'), + (0x2F927, 'M', u'𤠔'), + (0x2F928, 'M', u'獺'), + (0x2F929, 'M', u'王'), + (0x2F92A, 'M', u'㺬'), + (0x2F92B, 'M', u'玥'), + (0x2F92C, 'M', u'㺸'), + (0x2F92E, 'M', u'瑇'), + (0x2F92F, 'M', u'瑜'), + (0x2F930, 'M', u'瑱'), + (0x2F931, 'M', u'璅'), + (0x2F932, 'M', u'瓊'), + (0x2F933, 'M', u'㼛'), + (0x2F934, 'M', u'甤'), + (0x2F935, 'M', u'𤰶'), + (0x2F936, 'M', u'甾'), + (0x2F937, 'M', u'𤲒'), + (0x2F938, 'M', u'異'), + (0x2F939, 'M', u'𢆟'), + (0x2F93A, 'M', u'瘐'), + (0x2F93B, 'M', u'𤾡'), + (0x2F93C, 'M', u'𤾸'), + (0x2F93D, 'M', u'𥁄'), + (0x2F93E, 'M', u'㿼'), + (0x2F93F, 'M', u'䀈'), + (0x2F940, 'M', u'直'), + (0x2F941, 'M', u'𥃳'), + (0x2F942, 'M', u'𥃲'), + (0x2F943, 'M', u'𥄙'), + (0x2F944, 'M', u'𥄳'), + (0x2F945, 'M', u'眞'), + (0x2F946, 'M', u'真'), + (0x2F948, 'M', u'睊'), + (0x2F949, 'M', u'䀹'), + (0x2F94A, 'M', u'瞋'), + (0x2F94B, 'M', u'䁆'), + (0x2F94C, 'M', u'䂖'), + (0x2F94D, 'M', u'𥐝'), + (0x2F94E, 'M', u'硎'), + (0x2F94F, 'M', u'碌'), + (0x2F950, 'M', u'磌'), + (0x2F951, 'M', u'䃣'), + (0x2F952, 'M', u'𥘦'), + (0x2F953, 'M', u'祖'), + (0x2F954, 'M', u'𥚚'), + (0x2F955, 'M', u'𥛅'), + ] + +def _seg_76(): + return [ + (0x2F956, 'M', u'福'), + (0x2F957, 'M', u'秫'), + (0x2F958, 'M', u'䄯'), + (0x2F959, 'M', u'穀'), + (0x2F95A, 'M', u'穊'), + (0x2F95B, 'M', u'穏'), + (0x2F95C, 'M', u'𥥼'), + (0x2F95D, 'M', u'𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', u'䈂'), + (0x2F961, 'M', u'𥮫'), + (0x2F962, 'M', u'篆'), + (0x2F963, 'M', u'築'), + (0x2F964, 'M', u'䈧'), + (0x2F965, 'M', u'𥲀'), + (0x2F966, 'M', u'糒'), + (0x2F967, 'M', u'䊠'), + (0x2F968, 'M', u'糨'), + (0x2F969, 'M', u'糣'), + (0x2F96A, 'M', u'紀'), + (0x2F96B, 'M', u'𥾆'), + (0x2F96C, 'M', u'絣'), + (0x2F96D, 'M', u'䌁'), + (0x2F96E, 'M', u'緇'), + (0x2F96F, 'M', u'縂'), + (0x2F970, 'M', u'繅'), + (0x2F971, 'M', u'䌴'), + (0x2F972, 'M', u'𦈨'), + (0x2F973, 'M', u'𦉇'), + (0x2F974, 'M', u'䍙'), + (0x2F975, 'M', u'𦋙'), + (0x2F976, 'M', u'罺'), + (0x2F977, 'M', u'𦌾'), + (0x2F978, 'M', u'羕'), + (0x2F979, 'M', u'翺'), + (0x2F97A, 'M', u'者'), + (0x2F97B, 'M', u'𦓚'), + (0x2F97C, 'M', u'𦔣'), + (0x2F97D, 'M', u'聠'), + (0x2F97E, 'M', u'𦖨'), + (0x2F97F, 'M', u'聰'), + (0x2F980, 'M', u'𣍟'), + (0x2F981, 'M', u'䏕'), + (0x2F982, 'M', u'育'), + (0x2F983, 'M', u'脃'), + (0x2F984, 'M', u'䐋'), + (0x2F985, 'M', u'脾'), + (0x2F986, 'M', u'媵'), + (0x2F987, 'M', u'𦞧'), + (0x2F988, 'M', u'𦞵'), + (0x2F989, 'M', u'𣎓'), + (0x2F98A, 'M', u'𣎜'), + (0x2F98B, 'M', u'舁'), + (0x2F98C, 'M', u'舄'), + (0x2F98D, 'M', u'辞'), + (0x2F98E, 'M', u'䑫'), + (0x2F98F, 'M', u'芑'), + (0x2F990, 'M', u'芋'), + (0x2F991, 'M', u'芝'), + (0x2F992, 'M', u'劳'), + (0x2F993, 'M', u'花'), + (0x2F994, 'M', u'芳'), + (0x2F995, 'M', u'芽'), + (0x2F996, 'M', u'苦'), + (0x2F997, 'M', u'𦬼'), + (0x2F998, 'M', u'若'), + (0x2F999, 'M', u'茝'), + (0x2F99A, 'M', u'荣'), + (0x2F99B, 'M', u'莭'), + (0x2F99C, 'M', u'茣'), + (0x2F99D, 'M', u'莽'), + (0x2F99E, 'M', u'菧'), + (0x2F99F, 'M', u'著'), + (0x2F9A0, 'M', u'荓'), + (0x2F9A1, 'M', u'菊'), + (0x2F9A2, 'M', u'菌'), + (0x2F9A3, 'M', u'菜'), + (0x2F9A4, 'M', u'𦰶'), + (0x2F9A5, 'M', u'𦵫'), + (0x2F9A6, 'M', u'𦳕'), + (0x2F9A7, 'M', u'䔫'), + (0x2F9A8, 'M', u'蓱'), + (0x2F9A9, 'M', u'蓳'), + (0x2F9AA, 'M', u'蔖'), + (0x2F9AB, 'M', u'𧏊'), + (0x2F9AC, 'M', u'蕤'), + (0x2F9AD, 'M', u'𦼬'), + (0x2F9AE, 'M', u'䕝'), + (0x2F9AF, 'M', u'䕡'), + (0x2F9B0, 'M', u'𦾱'), + (0x2F9B1, 'M', u'𧃒'), + (0x2F9B2, 'M', u'䕫'), + (0x2F9B3, 'M', u'虐'), + (0x2F9B4, 'M', u'虜'), + (0x2F9B5, 'M', u'虧'), + (0x2F9B6, 'M', u'虩'), + (0x2F9B7, 'M', u'蚩'), + (0x2F9B8, 'M', u'蚈'), + (0x2F9B9, 'M', u'蜎'), + (0x2F9BA, 'M', u'蛢'), + ] + +def _seg_77(): + return [ + (0x2F9BB, 'M', u'蝹'), + (0x2F9BC, 'M', u'蜨'), + (0x2F9BD, 'M', u'蝫'), + (0x2F9BE, 'M', u'螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', u'蟡'), + (0x2F9C1, 'M', u'蠁'), + (0x2F9C2, 'M', u'䗹'), + (0x2F9C3, 'M', u'衠'), + (0x2F9C4, 'M', u'衣'), + (0x2F9C5, 'M', u'𧙧'), + (0x2F9C6, 'M', u'裗'), + (0x2F9C7, 'M', u'裞'), + (0x2F9C8, 'M', u'䘵'), + (0x2F9C9, 'M', u'裺'), + (0x2F9CA, 'M', u'㒻'), + (0x2F9CB, 'M', u'𧢮'), + (0x2F9CC, 'M', u'𧥦'), + (0x2F9CD, 'M', u'䚾'), + (0x2F9CE, 'M', u'䛇'), + (0x2F9CF, 'M', u'誠'), + (0x2F9D0, 'M', u'諭'), + (0x2F9D1, 'M', u'變'), + (0x2F9D2, 'M', u'豕'), + (0x2F9D3, 'M', u'𧲨'), + (0x2F9D4, 'M', u'貫'), + (0x2F9D5, 'M', u'賁'), + (0x2F9D6, 'M', u'贛'), + (0x2F9D7, 'M', u'起'), + (0x2F9D8, 'M', u'𧼯'), + (0x2F9D9, 'M', u'𠠄'), + (0x2F9DA, 'M', u'跋'), + (0x2F9DB, 'M', u'趼'), + (0x2F9DC, 'M', u'跰'), + (0x2F9DD, 'M', u'𠣞'), + (0x2F9DE, 'M', u'軔'), + (0x2F9DF, 'M', u'輸'), + (0x2F9E0, 'M', u'𨗒'), + (0x2F9E1, 'M', u'𨗭'), + (0x2F9E2, 'M', u'邔'), + (0x2F9E3, 'M', u'郱'), + (0x2F9E4, 'M', u'鄑'), + (0x2F9E5, 'M', u'𨜮'), + (0x2F9E6, 'M', u'鄛'), + (0x2F9E7, 'M', u'鈸'), + (0x2F9E8, 'M', u'鋗'), + (0x2F9E9, 'M', u'鋘'), + (0x2F9EA, 'M', u'鉼'), + (0x2F9EB, 'M', u'鏹'), + (0x2F9EC, 'M', u'鐕'), + (0x2F9ED, 'M', u'𨯺'), + (0x2F9EE, 'M', u'開'), + (0x2F9EF, 'M', u'䦕'), + (0x2F9F0, 'M', u'閷'), + (0x2F9F1, 'M', u'𨵷'), + (0x2F9F2, 'M', u'䧦'), + (0x2F9F3, 'M', u'雃'), + (0x2F9F4, 'M', u'嶲'), + (0x2F9F5, 'M', u'霣'), + (0x2F9F6, 'M', u'𩅅'), + (0x2F9F7, 'M', u'𩈚'), + (0x2F9F8, 'M', u'䩮'), + (0x2F9F9, 'M', u'䩶'), + (0x2F9FA, 'M', u'韠'), + (0x2F9FB, 'M', u'𩐊'), + (0x2F9FC, 'M', u'䪲'), + (0x2F9FD, 'M', u'𩒖'), + (0x2F9FE, 'M', u'頋'), + (0x2FA00, 'M', u'頩'), + (0x2FA01, 'M', u'𩖶'), + (0x2FA02, 'M', u'飢'), + (0x2FA03, 'M', u'䬳'), + (0x2FA04, 'M', u'餩'), + (0x2FA05, 'M', u'馧'), + (0x2FA06, 'M', u'駂'), + (0x2FA07, 'M', u'駾'), + (0x2FA08, 'M', u'䯎'), + (0x2FA09, 'M', u'𩬰'), + (0x2FA0A, 'M', u'鬒'), + (0x2FA0B, 'M', u'鱀'), + (0x2FA0C, 'M', u'鳽'), + (0x2FA0D, 'M', u'䳎'), + (0x2FA0E, 'M', u'䳭'), + (0x2FA0F, 'M', u'鵧'), + (0x2FA10, 'M', u'𪃎'), + (0x2FA11, 'M', u'䳸'), + (0x2FA12, 'M', u'𪄅'), + (0x2FA13, 'M', u'𪈎'), + (0x2FA14, 'M', u'𪊑'), + (0x2FA15, 'M', u'麻'), + (0x2FA16, 'M', u'䵖'), + (0x2FA17, 'M', u'黹'), + (0x2FA18, 'M', u'黾'), + (0x2FA19, 'M', u'鼅'), + (0x2FA1A, 'M', u'鼏'), + (0x2FA1B, 'M', u'鼖'), + (0x2FA1C, 'M', u'鼻'), + (0x2FA1D, 'M', u'𪘀'), + (0x2FA1E, 'X'), + (0xE0100, 'I'), + ] + +def _seg_78(): + return [ + (0xE01F0, 'X'), + ] + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() +) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/ipaddress.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/ipaddress.py new file mode 100644 index 0000000..f2d0766 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/ipaddress.py @@ -0,0 +1,2419 @@ +# Copyright 2007 Google Inc. +# Licensed to PSF under a Contributor Agreement. + +"""A fast, lightweight IPv4/IPv6 manipulation library in Python. + +This library is used to create/poke/manipulate IPv4 and IPv6 addresses +and networks. + +""" + +from __future__ import unicode_literals + + +import itertools +import struct + +__version__ = '1.0.22' + +# Compatibility functions +_compat_int_types = (int,) +try: + _compat_int_types = (int, long) +except NameError: + pass +try: + _compat_str = unicode +except NameError: + _compat_str = str + assert bytes != str +if b'\0'[0] == 0: # Python 3 semantics + def _compat_bytes_to_byte_vals(byt): + return byt +else: + def _compat_bytes_to_byte_vals(byt): + return [struct.unpack(b'!B', b)[0] for b in byt] +try: + _compat_int_from_byte_vals = int.from_bytes +except AttributeError: + def _compat_int_from_byte_vals(bytvals, endianess): + assert endianess == 'big' + res = 0 + for bv in bytvals: + assert isinstance(bv, _compat_int_types) + res = (res << 8) + bv + return res + + +def _compat_to_bytes(intval, length, endianess): + assert isinstance(intval, _compat_int_types) + assert endianess == 'big' + if length == 4: + if intval < 0 or intval >= 2 ** 32: + raise struct.error("integer out of range for 'I' format code") + return struct.pack(b'!I', intval) + elif length == 16: + if intval < 0 or intval >= 2 ** 128: + raise struct.error("integer out of range for 'QQ' format code") + return struct.pack(b'!QQ', intval >> 64, intval & 0xffffffffffffffff) + else: + raise NotImplementedError() + + +if hasattr(int, 'bit_length'): + # Not int.bit_length , since that won't work in 2.7 where long exists + def _compat_bit_length(i): + return i.bit_length() +else: + def _compat_bit_length(i): + for res in itertools.count(): + if i >> res == 0: + return res + + +def _compat_range(start, end, step=1): + assert step > 0 + i = start + while i < end: + yield i + i += step + + +class _TotalOrderingMixin(object): + __slots__ = () + + # Helper that derives the other comparison operations from + # __lt__ and __eq__ + # We avoid functools.total_ordering because it doesn't handle + # NotImplemented correctly yet (http://bugs.python.org/issue10042) + def __eq__(self, other): + raise NotImplementedError + + def __ne__(self, other): + equal = self.__eq__(other) + if equal is NotImplemented: + return NotImplemented + return not equal + + def __lt__(self, other): + raise NotImplementedError + + def __le__(self, other): + less = self.__lt__(other) + if less is NotImplemented or not less: + return self.__eq__(other) + return less + + def __gt__(self, other): + less = self.__lt__(other) + if less is NotImplemented: + return NotImplemented + equal = self.__eq__(other) + if equal is NotImplemented: + return NotImplemented + return not (less or equal) + + def __ge__(self, other): + less = self.__lt__(other) + if less is NotImplemented: + return NotImplemented + return not less + + +IPV4LENGTH = 32 +IPV6LENGTH = 128 + + +class AddressValueError(ValueError): + """A Value Error related to the address.""" + + +class NetmaskValueError(ValueError): + """A Value Error related to the netmask.""" + + +def ip_address(address): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + + Returns: + An IPv4Address or IPv6Address object. + + Raises: + ValueError: if the *address* passed isn't either a v4 or a v6 + address + + """ + try: + return IPv4Address(address) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Address(address) + except (AddressValueError, NetmaskValueError): + pass + + if isinstance(address, bytes): + raise AddressValueError( + '%r does not appear to be an IPv4 or IPv6 address. ' + 'Did you pass in a bytes (str in Python 2) instead of' + ' a unicode object?' % address) + + raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % + address) + + +def ip_network(address, strict=True): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP network. Either IPv4 or + IPv6 networks may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + + Returns: + An IPv4Network or IPv6Network object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. Or if the network has host bits set. + + """ + try: + return IPv4Network(address, strict) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Network(address, strict) + except (AddressValueError, NetmaskValueError): + pass + + if isinstance(address, bytes): + raise AddressValueError( + '%r does not appear to be an IPv4 or IPv6 network. ' + 'Did you pass in a bytes (str in Python 2) instead of' + ' a unicode object?' % address) + + raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % + address) + + +def ip_interface(address): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + + Returns: + An IPv4Interface or IPv6Interface object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. + + Notes: + The IPv?Interface classes describe an Address on a particular + Network, so they're basically a combination of both the Address + and Network classes. + + """ + try: + return IPv4Interface(address) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Interface(address) + except (AddressValueError, NetmaskValueError): + pass + + raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' % + address) + + +def v4_int_to_packed(address): + """Represent an address as 4 packed bytes in network (big-endian) order. + + Args: + address: An integer representation of an IPv4 IP address. + + Returns: + The integer address packed as 4 bytes in network (big-endian) order. + + Raises: + ValueError: If the integer is negative or too large to be an + IPv4 IP address. + + """ + try: + return _compat_to_bytes(address, 4, 'big') + except (struct.error, OverflowError): + raise ValueError("Address negative or too large for IPv4") + + +def v6_int_to_packed(address): + """Represent an address as 16 packed bytes in network (big-endian) order. + + Args: + address: An integer representation of an IPv6 IP address. + + Returns: + The integer address packed as 16 bytes in network (big-endian) order. + + """ + try: + return _compat_to_bytes(address, 16, 'big') + except (struct.error, OverflowError): + raise ValueError("Address negative or too large for IPv6") + + +def _split_optional_netmask(address): + """Helper to split the netmask and raise AddressValueError if needed""" + addr = _compat_str(address).split('/') + if len(addr) > 2: + raise AddressValueError("Only one '/' permitted in %r" % address) + return addr + + +def _find_address_range(addresses): + """Find a sequence of sorted deduplicated IPv#Address. + + Args: + addresses: a list of IPv#Address objects. + + Yields: + A tuple containing the first and last IP addresses in the sequence. + + """ + it = iter(addresses) + first = last = next(it) + for ip in it: + if ip._ip != last._ip + 1: + yield first, last + first = ip + last = ip + yield first, last + + +def _count_righthand_zero_bits(number, bits): + """Count the number of zero bits on the right hand side. + + Args: + number: an integer. + bits: maximum number of bits to count. + + Returns: + The number of zero bits on the right hand side of the number. + + """ + if number == 0: + return bits + return min(bits, _compat_bit_length(~number & (number - 1))) + + +def summarize_address_range(first, last): + """Summarize a network range given the first and last IP addresses. + + Example: + >>> list(summarize_address_range(IPv4Address('192.0.2.0'), + ... IPv4Address('192.0.2.130'))) + ... #doctest: +NORMALIZE_WHITESPACE + [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'), + IPv4Network('192.0.2.130/32')] + + Args: + first: the first IPv4Address or IPv6Address in the range. + last: the last IPv4Address or IPv6Address in the range. + + Returns: + An iterator of the summarized IPv(4|6) network objects. + + Raise: + TypeError: + If the first and last objects are not IP addresses. + If the first and last objects are not the same version. + ValueError: + If the last object is not greater than the first. + If the version of the first address is not 4 or 6. + + """ + if (not (isinstance(first, _BaseAddress) and + isinstance(last, _BaseAddress))): + raise TypeError('first and last must be IP addresses, not networks') + if first.version != last.version: + raise TypeError("%s and %s are not of the same version" % ( + first, last)) + if first > last: + raise ValueError('last IP address must be greater than first') + + if first.version == 4: + ip = IPv4Network + elif first.version == 6: + ip = IPv6Network + else: + raise ValueError('unknown IP version') + + ip_bits = first._max_prefixlen + first_int = first._ip + last_int = last._ip + while first_int <= last_int: + nbits = min(_count_righthand_zero_bits(first_int, ip_bits), + _compat_bit_length(last_int - first_int + 1) - 1) + net = ip((first_int, ip_bits - nbits)) + yield net + first_int += 1 << nbits + if first_int - 1 == ip._ALL_ONES: + break + + +def _collapse_addresses_internal(addresses): + """Loops through the addresses, collapsing concurrent netblocks. + + Example: + + ip1 = IPv4Network('192.0.2.0/26') + ip2 = IPv4Network('192.0.2.64/26') + ip3 = IPv4Network('192.0.2.128/26') + ip4 = IPv4Network('192.0.2.192/26') + + _collapse_addresses_internal([ip1, ip2, ip3, ip4]) -> + [IPv4Network('192.0.2.0/24')] + + This shouldn't be called directly; it is called via + collapse_addresses([]). + + Args: + addresses: A list of IPv4Network's or IPv6Network's + + Returns: + A list of IPv4Network's or IPv6Network's depending on what we were + passed. + + """ + # First merge + to_merge = list(addresses) + subnets = {} + while to_merge: + net = to_merge.pop() + supernet = net.supernet() + existing = subnets.get(supernet) + if existing is None: + subnets[supernet] = net + elif existing != net: + # Merge consecutive subnets + del subnets[supernet] + to_merge.append(supernet) + # Then iterate over resulting networks, skipping subsumed subnets + last = None + for net in sorted(subnets.values()): + if last is not None: + # Since they are sorted, + # last.network_address <= net.network_address is a given. + if last.broadcast_address >= net.broadcast_address: + continue + yield net + last = net + + +def collapse_addresses(addresses): + """Collapse a list of IP objects. + + Example: + collapse_addresses([IPv4Network('192.0.2.0/25'), + IPv4Network('192.0.2.128/25')]) -> + [IPv4Network('192.0.2.0/24')] + + Args: + addresses: An iterator of IPv4Network or IPv6Network objects. + + Returns: + An iterator of the collapsed IPv(4|6)Network objects. + + Raises: + TypeError: If passed a list of mixed version objects. + + """ + addrs = [] + ips = [] + nets = [] + + # split IP addresses and networks + for ip in addresses: + if isinstance(ip, _BaseAddress): + if ips and ips[-1]._version != ip._version: + raise TypeError("%s and %s are not of the same version" % ( + ip, ips[-1])) + ips.append(ip) + elif ip._prefixlen == ip._max_prefixlen: + if ips and ips[-1]._version != ip._version: + raise TypeError("%s and %s are not of the same version" % ( + ip, ips[-1])) + try: + ips.append(ip.ip) + except AttributeError: + ips.append(ip.network_address) + else: + if nets and nets[-1]._version != ip._version: + raise TypeError("%s and %s are not of the same version" % ( + ip, nets[-1])) + nets.append(ip) + + # sort and dedup + ips = sorted(set(ips)) + + # find consecutive address ranges in the sorted sequence and summarize them + if ips: + for first, last in _find_address_range(ips): + addrs.extend(summarize_address_range(first, last)) + + return _collapse_addresses_internal(addrs + nets) + + +def get_mixed_type_key(obj): + """Return a key suitable for sorting between networks and addresses. + + Address and Network objects are not sortable by default; they're + fundamentally different so the expression + + IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24') + + doesn't make any sense. There are some times however, where you may wish + to have ipaddress sort these for you anyway. If you need to do this, you + can use this function as the key= argument to sorted(). + + Args: + obj: either a Network or Address object. + Returns: + appropriate key. + + """ + if isinstance(obj, _BaseNetwork): + return obj._get_networks_key() + elif isinstance(obj, _BaseAddress): + return obj._get_address_key() + return NotImplemented + + +class _IPAddressBase(_TotalOrderingMixin): + + """The mother class.""" + + __slots__ = () + + @property + def exploded(self): + """Return the longhand version of the IP address as a string.""" + return self._explode_shorthand_ip_string() + + @property + def compressed(self): + """Return the shorthand version of the IP address as a string.""" + return _compat_str(self) + + @property + def reverse_pointer(self): + """The name of the reverse DNS pointer for the IP address, e.g.: + >>> ipaddress.ip_address("127.0.0.1").reverse_pointer + '1.0.0.127.in-addr.arpa' + >>> ipaddress.ip_address("2001:db8::1").reverse_pointer + '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' + + """ + return self._reverse_pointer() + + @property + def version(self): + msg = '%200s has no version specified' % (type(self),) + raise NotImplementedError(msg) + + def _check_int_address(self, address): + if address < 0: + msg = "%d (< 0) is not permitted as an IPv%d address" + raise AddressValueError(msg % (address, self._version)) + if address > self._ALL_ONES: + msg = "%d (>= 2**%d) is not permitted as an IPv%d address" + raise AddressValueError(msg % (address, self._max_prefixlen, + self._version)) + + def _check_packed_address(self, address, expected_len): + address_len = len(address) + if address_len != expected_len: + msg = ( + '%r (len %d != %d) is not permitted as an IPv%d address. ' + 'Did you pass in a bytes (str in Python 2) instead of' + ' a unicode object?') + raise AddressValueError(msg % (address, address_len, + expected_len, self._version)) + + @classmethod + def _ip_int_from_prefix(cls, prefixlen): + """Turn the prefix length into a bitwise netmask + + Args: + prefixlen: An integer, the prefix length. + + Returns: + An integer. + + """ + return cls._ALL_ONES ^ (cls._ALL_ONES >> prefixlen) + + @classmethod + def _prefix_from_ip_int(cls, ip_int): + """Return prefix length from the bitwise netmask. + + Args: + ip_int: An integer, the netmask in expanded bitwise format + + Returns: + An integer, the prefix length. + + Raises: + ValueError: If the input intermingles zeroes & ones + """ + trailing_zeroes = _count_righthand_zero_bits(ip_int, + cls._max_prefixlen) + prefixlen = cls._max_prefixlen - trailing_zeroes + leading_ones = ip_int >> trailing_zeroes + all_ones = (1 << prefixlen) - 1 + if leading_ones != all_ones: + byteslen = cls._max_prefixlen // 8 + details = _compat_to_bytes(ip_int, byteslen, 'big') + msg = 'Netmask pattern %r mixes zeroes & ones' + raise ValueError(msg % details) + return prefixlen + + @classmethod + def _report_invalid_netmask(cls, netmask_str): + msg = '%r is not a valid netmask' % netmask_str + raise NetmaskValueError(msg) + + @classmethod + def _prefix_from_prefix_string(cls, prefixlen_str): + """Return prefix length from a numeric string + + Args: + prefixlen_str: The string to be converted + + Returns: + An integer, the prefix length. + + Raises: + NetmaskValueError: If the input is not a valid netmask + """ + # int allows a leading +/- as well as surrounding whitespace, + # so we ensure that isn't the case + if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str): + cls._report_invalid_netmask(prefixlen_str) + try: + prefixlen = int(prefixlen_str) + except ValueError: + cls._report_invalid_netmask(prefixlen_str) + if not (0 <= prefixlen <= cls._max_prefixlen): + cls._report_invalid_netmask(prefixlen_str) + return prefixlen + + @classmethod + def _prefix_from_ip_string(cls, ip_str): + """Turn a netmask/hostmask string into a prefix length + + Args: + ip_str: The netmask/hostmask to be converted + + Returns: + An integer, the prefix length. + + Raises: + NetmaskValueError: If the input is not a valid netmask/hostmask + """ + # Parse the netmask/hostmask like an IP address. + try: + ip_int = cls._ip_int_from_string(ip_str) + except AddressValueError: + cls._report_invalid_netmask(ip_str) + + # Try matching a netmask (this would be /1*0*/ as a bitwise regexp). + # Note that the two ambiguous cases (all-ones and all-zeroes) are + # treated as netmasks. + try: + return cls._prefix_from_ip_int(ip_int) + except ValueError: + pass + + # Invert the bits, and try matching a /0+1+/ hostmask instead. + ip_int ^= cls._ALL_ONES + try: + return cls._prefix_from_ip_int(ip_int) + except ValueError: + cls._report_invalid_netmask(ip_str) + + def __reduce__(self): + return self.__class__, (_compat_str(self),) + + +class _BaseAddress(_IPAddressBase): + + """A generic IP object. + + This IP class contains the version independent methods which are + used by single IP addresses. + """ + + __slots__ = () + + def __int__(self): + return self._ip + + def __eq__(self, other): + try: + return (self._ip == other._ip and + self._version == other._version) + except AttributeError: + return NotImplemented + + def __lt__(self, other): + if not isinstance(other, _IPAddressBase): + return NotImplemented + if not isinstance(other, _BaseAddress): + raise TypeError('%s and %s are not of the same type' % ( + self, other)) + if self._version != other._version: + raise TypeError('%s and %s are not of the same version' % ( + self, other)) + if self._ip != other._ip: + return self._ip < other._ip + return False + + # Shorthand for Integer addition and subtraction. This is not + # meant to ever support addition/subtraction of addresses. + def __add__(self, other): + if not isinstance(other, _compat_int_types): + return NotImplemented + return self.__class__(int(self) + other) + + def __sub__(self, other): + if not isinstance(other, _compat_int_types): + return NotImplemented + return self.__class__(int(self) - other) + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) + + def __str__(self): + return _compat_str(self._string_from_ip_int(self._ip)) + + def __hash__(self): + return hash(hex(int(self._ip))) + + def _get_address_key(self): + return (self._version, self) + + def __reduce__(self): + return self.__class__, (self._ip,) + + +class _BaseNetwork(_IPAddressBase): + + """A generic IP network object. + + This IP class contains the version independent methods which are + used by networks. + + """ + def __init__(self, address): + self._cache = {} + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) + + def __str__(self): + return '%s/%d' % (self.network_address, self.prefixlen) + + def hosts(self): + """Generate Iterator over usable hosts in a network. + + This is like __iter__ except it doesn't return the network + or broadcast addresses. + + """ + network = int(self.network_address) + broadcast = int(self.broadcast_address) + for x in _compat_range(network + 1, broadcast): + yield self._address_class(x) + + def __iter__(self): + network = int(self.network_address) + broadcast = int(self.broadcast_address) + for x in _compat_range(network, broadcast + 1): + yield self._address_class(x) + + def __getitem__(self, n): + network = int(self.network_address) + broadcast = int(self.broadcast_address) + if n >= 0: + if network + n > broadcast: + raise IndexError('address out of range') + return self._address_class(network + n) + else: + n += 1 + if broadcast + n < network: + raise IndexError('address out of range') + return self._address_class(broadcast + n) + + def __lt__(self, other): + if not isinstance(other, _IPAddressBase): + return NotImplemented + if not isinstance(other, _BaseNetwork): + raise TypeError('%s and %s are not of the same type' % ( + self, other)) + if self._version != other._version: + raise TypeError('%s and %s are not of the same version' % ( + self, other)) + if self.network_address != other.network_address: + return self.network_address < other.network_address + if self.netmask != other.netmask: + return self.netmask < other.netmask + return False + + def __eq__(self, other): + try: + return (self._version == other._version and + self.network_address == other.network_address and + int(self.netmask) == int(other.netmask)) + except AttributeError: + return NotImplemented + + def __hash__(self): + return hash(int(self.network_address) ^ int(self.netmask)) + + def __contains__(self, other): + # always false if one is v4 and the other is v6. + if self._version != other._version: + return False + # dealing with another network. + if isinstance(other, _BaseNetwork): + return False + # dealing with another address + else: + # address + return (int(self.network_address) <= int(other._ip) <= + int(self.broadcast_address)) + + def overlaps(self, other): + """Tell if self is partly contained in other.""" + return self.network_address in other or ( + self.broadcast_address in other or ( + other.network_address in self or ( + other.broadcast_address in self))) + + @property + def broadcast_address(self): + x = self._cache.get('broadcast_address') + if x is None: + x = self._address_class(int(self.network_address) | + int(self.hostmask)) + self._cache['broadcast_address'] = x + return x + + @property + def hostmask(self): + x = self._cache.get('hostmask') + if x is None: + x = self._address_class(int(self.netmask) ^ self._ALL_ONES) + self._cache['hostmask'] = x + return x + + @property + def with_prefixlen(self): + return '%s/%d' % (self.network_address, self._prefixlen) + + @property + def with_netmask(self): + return '%s/%s' % (self.network_address, self.netmask) + + @property + def with_hostmask(self): + return '%s/%s' % (self.network_address, self.hostmask) + + @property + def num_addresses(self): + """Number of hosts in the current subnet.""" + return int(self.broadcast_address) - int(self.network_address) + 1 + + @property + def _address_class(self): + # Returning bare address objects (rather than interfaces) allows for + # more consistent behaviour across the network address, broadcast + # address and individual host addresses. + msg = '%200s has no associated address class' % (type(self),) + raise NotImplementedError(msg) + + @property + def prefixlen(self): + return self._prefixlen + + def address_exclude(self, other): + """Remove an address from a larger block. + + For example: + + addr1 = ip_network('192.0.2.0/28') + addr2 = ip_network('192.0.2.1/32') + list(addr1.address_exclude(addr2)) = + [IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'), + IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29')] + + or IPv6: + + addr1 = ip_network('2001:db8::1/32') + addr2 = ip_network('2001:db8::1/128') + list(addr1.address_exclude(addr2)) = + [ip_network('2001:db8::1/128'), + ip_network('2001:db8::2/127'), + ip_network('2001:db8::4/126'), + ip_network('2001:db8::8/125'), + ... + ip_network('2001:db8:8000::/33')] + + Args: + other: An IPv4Network or IPv6Network object of the same type. + + Returns: + An iterator of the IPv(4|6)Network objects which is self + minus other. + + Raises: + TypeError: If self and other are of differing address + versions, or if other is not a network object. + ValueError: If other is not completely contained by self. + + """ + if not self._version == other._version: + raise TypeError("%s and %s are not of the same version" % ( + self, other)) + + if not isinstance(other, _BaseNetwork): + raise TypeError("%s is not a network object" % other) + + if not other.subnet_of(self): + raise ValueError('%s not contained in %s' % (other, self)) + if other == self: + return + + # Make sure we're comparing the network of other. + other = other.__class__('%s/%s' % (other.network_address, + other.prefixlen)) + + s1, s2 = self.subnets() + while s1 != other and s2 != other: + if other.subnet_of(s1): + yield s2 + s1, s2 = s1.subnets() + elif other.subnet_of(s2): + yield s1 + s1, s2 = s2.subnets() + else: + # If we got here, there's a bug somewhere. + raise AssertionError('Error performing exclusion: ' + 's1: %s s2: %s other: %s' % + (s1, s2, other)) + if s1 == other: + yield s2 + elif s2 == other: + yield s1 + else: + # If we got here, there's a bug somewhere. + raise AssertionError('Error performing exclusion: ' + 's1: %s s2: %s other: %s' % + (s1, s2, other)) + + def compare_networks(self, other): + """Compare two IP objects. + + This is only concerned about the comparison of the integer + representation of the network addresses. This means that the + host bits aren't considered at all in this method. If you want + to compare host bits, you can easily enough do a + 'HostA._ip < HostB._ip' + + Args: + other: An IP object. + + Returns: + If the IP versions of self and other are the same, returns: + + -1 if self < other: + eg: IPv4Network('192.0.2.0/25') < IPv4Network('192.0.2.128/25') + IPv6Network('2001:db8::1000/124') < + IPv6Network('2001:db8::2000/124') + 0 if self == other + eg: IPv4Network('192.0.2.0/24') == IPv4Network('192.0.2.0/24') + IPv6Network('2001:db8::1000/124') == + IPv6Network('2001:db8::1000/124') + 1 if self > other + eg: IPv4Network('192.0.2.128/25') > IPv4Network('192.0.2.0/25') + IPv6Network('2001:db8::2000/124') > + IPv6Network('2001:db8::1000/124') + + Raises: + TypeError if the IP versions are different. + + """ + # does this need to raise a ValueError? + if self._version != other._version: + raise TypeError('%s and %s are not of the same type' % ( + self, other)) + # self._version == other._version below here: + if self.network_address < other.network_address: + return -1 + if self.network_address > other.network_address: + return 1 + # self.network_address == other.network_address below here: + if self.netmask < other.netmask: + return -1 + if self.netmask > other.netmask: + return 1 + return 0 + + def _get_networks_key(self): + """Network-only key function. + + Returns an object that identifies this address' network and + netmask. This function is a suitable "key" argument for sorted() + and list.sort(). + + """ + return (self._version, self.network_address, self.netmask) + + def subnets(self, prefixlen_diff=1, new_prefix=None): + """The subnets which join to make the current subnet. + + In the case that self contains only one IP + (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 + for IPv6), yield an iterator with just ourself. + + Args: + prefixlen_diff: An integer, the amount the prefix length + should be increased by. This should not be set if + new_prefix is also set. + new_prefix: The desired new prefix length. This must be a + larger number (smaller prefix) than the existing prefix. + This should not be set if prefixlen_diff is also set. + + Returns: + An iterator of IPv(4|6) objects. + + Raises: + ValueError: The prefixlen_diff is too small or too large. + OR + prefixlen_diff and new_prefix are both set or new_prefix + is a smaller number than the current prefix (smaller + number means a larger network) + + """ + if self._prefixlen == self._max_prefixlen: + yield self + return + + if new_prefix is not None: + if new_prefix < self._prefixlen: + raise ValueError('new prefix must be longer') + if prefixlen_diff != 1: + raise ValueError('cannot set prefixlen_diff and new_prefix') + prefixlen_diff = new_prefix - self._prefixlen + + if prefixlen_diff < 0: + raise ValueError('prefix length diff must be > 0') + new_prefixlen = self._prefixlen + prefixlen_diff + + if new_prefixlen > self._max_prefixlen: + raise ValueError( + 'prefix length diff %d is invalid for netblock %s' % ( + new_prefixlen, self)) + + start = int(self.network_address) + end = int(self.broadcast_address) + 1 + step = (int(self.hostmask) + 1) >> prefixlen_diff + for new_addr in _compat_range(start, end, step): + current = self.__class__((new_addr, new_prefixlen)) + yield current + + def supernet(self, prefixlen_diff=1, new_prefix=None): + """The supernet containing the current network. + + Args: + prefixlen_diff: An integer, the amount the prefix length of + the network should be decreased by. For example, given a + /24 network and a prefixlen_diff of 3, a supernet with a + /21 netmask is returned. + + Returns: + An IPv4 network object. + + Raises: + ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have + a negative prefix length. + OR + If prefixlen_diff and new_prefix are both set or new_prefix is a + larger number than the current prefix (larger number means a + smaller network) + + """ + if self._prefixlen == 0: + return self + + if new_prefix is not None: + if new_prefix > self._prefixlen: + raise ValueError('new prefix must be shorter') + if prefixlen_diff != 1: + raise ValueError('cannot set prefixlen_diff and new_prefix') + prefixlen_diff = self._prefixlen - new_prefix + + new_prefixlen = self.prefixlen - prefixlen_diff + if new_prefixlen < 0: + raise ValueError( + 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % + (self.prefixlen, prefixlen_diff)) + return self.__class__(( + int(self.network_address) & (int(self.netmask) << prefixlen_diff), + new_prefixlen)) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is a multicast address. + See RFC 2373 2.7 for details. + + """ + return (self.network_address.is_multicast and + self.broadcast_address.is_multicast) + + @staticmethod + def _is_subnet_of(a, b): + try: + # Always false if one is v4 and the other is v6. + if a._version != b._version: + raise TypeError("%s and %s are not of the same version" (a, b)) + return (b.network_address <= a.network_address and + b.broadcast_address >= a.broadcast_address) + except AttributeError: + raise TypeError("Unable to test subnet containment " + "between %s and %s" % (a, b)) + + def subnet_of(self, other): + """Return True if this network is a subnet of other.""" + return self._is_subnet_of(self, other) + + def supernet_of(self, other): + """Return True if this network is a supernet of other.""" + return self._is_subnet_of(other, self) + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within one of the + reserved IPv6 Network ranges. + + """ + return (self.network_address.is_reserved and + self.broadcast_address.is_reserved) + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is reserved per RFC 4291. + + """ + return (self.network_address.is_link_local and + self.broadcast_address.is_link_local) + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per + iana-ipv4-special-registry or iana-ipv6-special-registry. + + """ + return (self.network_address.is_private and + self.broadcast_address.is_private) + + @property + def is_global(self): + """Test if this address is allocated for public networks. + + Returns: + A boolean, True if the address is not reserved per + iana-ipv4-special-registry or iana-ipv6-special-registry. + + """ + return not self.is_private + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 2373 2.5.2. + + """ + return (self.network_address.is_unspecified and + self.broadcast_address.is_unspecified) + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback address as defined in + RFC 2373 2.5.3. + + """ + return (self.network_address.is_loopback and + self.broadcast_address.is_loopback) + + +class _BaseV4(object): + + """Base IPv4 object. + + The following methods are used by IPv4 objects in both single IP + addresses and networks. + + """ + + __slots__ = () + _version = 4 + # Equivalent to 255.255.255.255 or 32 bits of 1's. + _ALL_ONES = (2 ** IPV4LENGTH) - 1 + _DECIMAL_DIGITS = frozenset('0123456789') + + # the valid octets for host and netmasks. only useful for IPv4. + _valid_mask_octets = frozenset([255, 254, 252, 248, 240, 224, 192, 128, 0]) + + _max_prefixlen = IPV4LENGTH + # There are only a handful of valid v4 netmasks, so we cache them all + # when constructed (see _make_netmask()). + _netmask_cache = {} + + def _explode_shorthand_ip_string(self): + return _compat_str(self) + + @classmethod + def _make_netmask(cls, arg): + """Make a (netmask, prefix_len) tuple from the given argument. + + Argument can be: + - an integer (the prefix length) + - a string representing the prefix length (e.g. "24") + - a string representing the prefix netmask (e.g. "255.255.255.0") + """ + if arg not in cls._netmask_cache: + if isinstance(arg, _compat_int_types): + prefixlen = arg + else: + try: + # Check for a netmask in prefix length form + prefixlen = cls._prefix_from_prefix_string(arg) + except NetmaskValueError: + # Check for a netmask or hostmask in dotted-quad form. + # This may raise NetmaskValueError. + prefixlen = cls._prefix_from_ip_string(arg) + netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) + cls._netmask_cache[arg] = netmask, prefixlen + return cls._netmask_cache[arg] + + @classmethod + def _ip_int_from_string(cls, ip_str): + """Turn the given IP string into an integer for comparison. + + Args: + ip_str: A string, the IP ip_str. + + Returns: + The IP ip_str as an integer. + + Raises: + AddressValueError: if ip_str isn't a valid IPv4 Address. + + """ + if not ip_str: + raise AddressValueError('Address cannot be empty') + + octets = ip_str.split('.') + if len(octets) != 4: + raise AddressValueError("Expected 4 octets in %r" % ip_str) + + try: + return _compat_int_from_byte_vals( + map(cls._parse_octet, octets), 'big') + except ValueError as exc: + raise AddressValueError("%s in %r" % (exc, ip_str)) + + @classmethod + def _parse_octet(cls, octet_str): + """Convert a decimal octet into an integer. + + Args: + octet_str: A string, the number to parse. + + Returns: + The octet as an integer. + + Raises: + ValueError: if the octet isn't strictly a decimal from [0..255]. + + """ + if not octet_str: + raise ValueError("Empty octet not permitted") + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not cls._DECIMAL_DIGITS.issuperset(octet_str): + msg = "Only decimal digits permitted in %r" + raise ValueError(msg % octet_str) + # We do the length check second, since the invalid character error + # is likely to be more informative for the user + if len(octet_str) > 3: + msg = "At most 3 characters permitted in %r" + raise ValueError(msg % octet_str) + # Convert to integer (we know digits are legal) + octet_int = int(octet_str, 10) + # Any octets that look like they *might* be written in octal, + # and which don't look exactly the same in both octal and + # decimal are rejected as ambiguous + if octet_int > 7 and octet_str[0] == '0': + msg = "Ambiguous (octal/decimal) value in %r not permitted" + raise ValueError(msg % octet_str) + if octet_int > 255: + raise ValueError("Octet %d (> 255) not permitted" % octet_int) + return octet_int + + @classmethod + def _string_from_ip_int(cls, ip_int): + """Turns a 32-bit integer into dotted decimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + The IP address as a string in dotted decimal notation. + + """ + return '.'.join(_compat_str(struct.unpack(b'!B', b)[0] + if isinstance(b, bytes) + else b) + for b in _compat_to_bytes(ip_int, 4, 'big')) + + def _is_hostmask(self, ip_str): + """Test if the IP string is a hostmask (rather than a netmask). + + Args: + ip_str: A string, the potential hostmask. + + Returns: + A boolean, True if the IP string is a hostmask. + + """ + bits = ip_str.split('.') + try: + parts = [x for x in map(int, bits) if x in self._valid_mask_octets] + except ValueError: + return False + if len(parts) != len(bits): + return False + if parts[0] < parts[-1]: + return True + return False + + def _reverse_pointer(self): + """Return the reverse DNS pointer name for the IPv4 address. + + This implements the method described in RFC1035 3.5. + + """ + reverse_octets = _compat_str(self).split('.')[::-1] + return '.'.join(reverse_octets) + '.in-addr.arpa' + + @property + def max_prefixlen(self): + return self._max_prefixlen + + @property + def version(self): + return self._version + + +class IPv4Address(_BaseV4, _BaseAddress): + + """Represent and manipulate single IPv4 Addresses.""" + + __slots__ = ('_ip', '__weakref__') + + def __init__(self, address): + + """ + Args: + address: A string or integer representing the IP + + Additionally, an integer can be passed, so + IPv4Address('192.0.2.1') == IPv4Address(3221225985). + or, more generally + IPv4Address(int(IPv4Address('192.0.2.1'))) == + IPv4Address('192.0.2.1') + + Raises: + AddressValueError: If ipaddress isn't a valid IPv4 address. + + """ + # Efficient constructor from integer. + if isinstance(address, _compat_int_types): + self._check_int_address(address) + self._ip = address + return + + # Constructing from a packed address + if isinstance(address, bytes): + self._check_packed_address(address, 4) + bvs = _compat_bytes_to_byte_vals(address) + self._ip = _compat_int_from_byte_vals(bvs, 'big') + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP string. + addr_str = _compat_str(address) + if '/' in addr_str: + raise AddressValueError("Unexpected '/' in %r" % address) + self._ip = self._ip_int_from_string(addr_str) + + @property + def packed(self): + """The binary representation of this address.""" + return v4_int_to_packed(self._ip) + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within the + reserved IPv4 Network range. + + """ + return self in self._constants._reserved_network + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per + iana-ipv4-special-registry. + + """ + return any(self in net for net in self._constants._private_networks) + + @property + def is_global(self): + return ( + self not in self._constants._public_network and + not self.is_private) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is multicast. + See RFC 3171 for details. + + """ + return self in self._constants._multicast_network + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 5735 3. + + """ + return self == self._constants._unspecified_address + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback per RFC 3330. + + """ + return self in self._constants._loopback_network + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is link-local per RFC 3927. + + """ + return self in self._constants._linklocal_network + + +class IPv4Interface(IPv4Address): + + def __init__(self, address): + if isinstance(address, (bytes, _compat_int_types)): + IPv4Address.__init__(self, address) + self.network = IPv4Network(self._ip) + self._prefixlen = self._max_prefixlen + return + + if isinstance(address, tuple): + IPv4Address.__init__(self, address[0]) + if len(address) > 1: + self._prefixlen = int(address[1]) + else: + self._prefixlen = self._max_prefixlen + + self.network = IPv4Network(address, strict=False) + self.netmask = self.network.netmask + self.hostmask = self.network.hostmask + return + + addr = _split_optional_netmask(address) + IPv4Address.__init__(self, addr[0]) + + self.network = IPv4Network(address, strict=False) + self._prefixlen = self.network._prefixlen + + self.netmask = self.network.netmask + self.hostmask = self.network.hostmask + + def __str__(self): + return '%s/%d' % (self._string_from_ip_int(self._ip), + self.network.prefixlen) + + def __eq__(self, other): + address_equal = IPv4Address.__eq__(self, other) + if not address_equal or address_equal is NotImplemented: + return address_equal + try: + return self.network == other.network + except AttributeError: + # An interface with an associated network is NOT the + # same as an unassociated address. That's why the hash + # takes the extra info into account. + return False + + def __lt__(self, other): + address_less = IPv4Address.__lt__(self, other) + if address_less is NotImplemented: + return NotImplemented + try: + return (self.network < other.network or + self.network == other.network and address_less) + except AttributeError: + # We *do* allow addresses and interfaces to be sorted. The + # unassociated address is considered less than all interfaces. + return False + + def __hash__(self): + return self._ip ^ self._prefixlen ^ int(self.network.network_address) + + __reduce__ = _IPAddressBase.__reduce__ + + @property + def ip(self): + return IPv4Address(self._ip) + + @property + def with_prefixlen(self): + return '%s/%s' % (self._string_from_ip_int(self._ip), + self._prefixlen) + + @property + def with_netmask(self): + return '%s/%s' % (self._string_from_ip_int(self._ip), + self.netmask) + + @property + def with_hostmask(self): + return '%s/%s' % (self._string_from_ip_int(self._ip), + self.hostmask) + + +class IPv4Network(_BaseV4, _BaseNetwork): + + """This class represents and manipulates 32-bit IPv4 network + addresses.. + + Attributes: [examples for IPv4Network('192.0.2.0/27')] + .network_address: IPv4Address('192.0.2.0') + .hostmask: IPv4Address('0.0.0.31') + .broadcast_address: IPv4Address('192.0.2.32') + .netmask: IPv4Address('255.255.255.224') + .prefixlen: 27 + + """ + # Class to use when creating address objects + _address_class = IPv4Address + + def __init__(self, address, strict=True): + + """Instantiate a new IPv4 network object. + + Args: + address: A string or integer representing the IP [& network]. + '192.0.2.0/24' + '192.0.2.0/255.255.255.0' + '192.0.0.2/0.0.0.255' + are all functionally the same in IPv4. Similarly, + '192.0.2.1' + '192.0.2.1/255.255.255.255' + '192.0.2.1/32' + are also functionally equivalent. That is to say, failing to + provide a subnetmask will create an object with a mask of /32. + + If the mask (portion after the / in the argument) is given in + dotted quad form, it is treated as a netmask if it starts with a + non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it + starts with a zero field (e.g. 0.255.255.255 == /8), with the + single exception of an all-zero mask which is treated as a + netmask == /0. If no mask is given, a default of /32 is used. + + Additionally, an integer can be passed, so + IPv4Network('192.0.2.1') == IPv4Network(3221225985) + or, more generally + IPv4Interface(int(IPv4Interface('192.0.2.1'))) == + IPv4Interface('192.0.2.1') + + Raises: + AddressValueError: If ipaddress isn't a valid IPv4 address. + NetmaskValueError: If the netmask isn't valid for + an IPv4 address. + ValueError: If strict is True and a network address is not + supplied. + + """ + _BaseNetwork.__init__(self, address) + + # Constructing from a packed address or integer + if isinstance(address, (_compat_int_types, bytes)): + self.network_address = IPv4Address(address) + self.netmask, self._prefixlen = self._make_netmask( + self._max_prefixlen) + # fixme: address/network test here. + return + + if isinstance(address, tuple): + if len(address) > 1: + arg = address[1] + else: + # We weren't given an address[1] + arg = self._max_prefixlen + self.network_address = IPv4Address(address[0]) + self.netmask, self._prefixlen = self._make_netmask(arg) + packed = int(self.network_address) + if packed & int(self.netmask) != packed: + if strict: + raise ValueError('%s has host bits set' % self) + else: + self.network_address = IPv4Address(packed & + int(self.netmask)) + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = _split_optional_netmask(address) + self.network_address = IPv4Address(self._ip_int_from_string(addr[0])) + + if len(addr) == 2: + arg = addr[1] + else: + arg = self._max_prefixlen + self.netmask, self._prefixlen = self._make_netmask(arg) + + if strict: + if (IPv4Address(int(self.network_address) & int(self.netmask)) != + self.network_address): + raise ValueError('%s has host bits set' % self) + self.network_address = IPv4Address(int(self.network_address) & + int(self.netmask)) + + if self._prefixlen == (self._max_prefixlen - 1): + self.hosts = self.__iter__ + + @property + def is_global(self): + """Test if this address is allocated for public networks. + + Returns: + A boolean, True if the address is not reserved per + iana-ipv4-special-registry. + + """ + return (not (self.network_address in IPv4Network('100.64.0.0/10') and + self.broadcast_address in IPv4Network('100.64.0.0/10')) and + not self.is_private) + + +class _IPv4Constants(object): + + _linklocal_network = IPv4Network('169.254.0.0/16') + + _loopback_network = IPv4Network('127.0.0.0/8') + + _multicast_network = IPv4Network('224.0.0.0/4') + + _public_network = IPv4Network('100.64.0.0/10') + + _private_networks = [ + IPv4Network('0.0.0.0/8'), + IPv4Network('10.0.0.0/8'), + IPv4Network('127.0.0.0/8'), + IPv4Network('169.254.0.0/16'), + IPv4Network('172.16.0.0/12'), + IPv4Network('192.0.0.0/29'), + IPv4Network('192.0.0.170/31'), + IPv4Network('192.0.2.0/24'), + IPv4Network('192.168.0.0/16'), + IPv4Network('198.18.0.0/15'), + IPv4Network('198.51.100.0/24'), + IPv4Network('203.0.113.0/24'), + IPv4Network('240.0.0.0/4'), + IPv4Network('255.255.255.255/32'), + ] + + _reserved_network = IPv4Network('240.0.0.0/4') + + _unspecified_address = IPv4Address('0.0.0.0') + + +IPv4Address._constants = _IPv4Constants + + +class _BaseV6(object): + + """Base IPv6 object. + + The following methods are used by IPv6 objects in both single IP + addresses and networks. + + """ + + __slots__ = () + _version = 6 + _ALL_ONES = (2 ** IPV6LENGTH) - 1 + _HEXTET_COUNT = 8 + _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') + _max_prefixlen = IPV6LENGTH + + # There are only a bunch of valid v6 netmasks, so we cache them all + # when constructed (see _make_netmask()). + _netmask_cache = {} + + @classmethod + def _make_netmask(cls, arg): + """Make a (netmask, prefix_len) tuple from the given argument. + + Argument can be: + - an integer (the prefix length) + - a string representing the prefix length (e.g. "24") + - a string representing the prefix netmask (e.g. "255.255.255.0") + """ + if arg not in cls._netmask_cache: + if isinstance(arg, _compat_int_types): + prefixlen = arg + else: + prefixlen = cls._prefix_from_prefix_string(arg) + netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) + cls._netmask_cache[arg] = netmask, prefixlen + return cls._netmask_cache[arg] + + @classmethod + def _ip_int_from_string(cls, ip_str): + """Turn an IPv6 ip_str into an integer. + + Args: + ip_str: A string, the IPv6 ip_str. + + Returns: + An int, the IPv6 address + + Raises: + AddressValueError: if ip_str isn't a valid IPv6 Address. + + """ + if not ip_str: + raise AddressValueError('Address cannot be empty') + + parts = ip_str.split(':') + + # An IPv6 address needs at least 2 colons (3 parts). + _min_parts = 3 + if len(parts) < _min_parts: + msg = "At least %d parts expected in %r" % (_min_parts, ip_str) + raise AddressValueError(msg) + + # If the address has an IPv4-style suffix, convert it to hexadecimal. + if '.' in parts[-1]: + try: + ipv4_int = IPv4Address(parts.pop())._ip + except AddressValueError as exc: + raise AddressValueError("%s in %r" % (exc, ip_str)) + parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) + parts.append('%x' % (ipv4_int & 0xFFFF)) + + # An IPv6 address can't have more than 8 colons (9 parts). + # The extra colon comes from using the "::" notation for a single + # leading or trailing zero part. + _max_parts = cls._HEXTET_COUNT + 1 + if len(parts) > _max_parts: + msg = "At most %d colons permitted in %r" % ( + _max_parts - 1, ip_str) + raise AddressValueError(msg) + + # Disregarding the endpoints, find '::' with nothing in between. + # This indicates that a run of zeroes has been skipped. + skip_index = None + for i in _compat_range(1, len(parts) - 1): + if not parts[i]: + if skip_index is not None: + # Can't have more than one '::' + msg = "At most one '::' permitted in %r" % ip_str + raise AddressValueError(msg) + skip_index = i + + # parts_hi is the number of parts to copy from above/before the '::' + # parts_lo is the number of parts to copy from below/after the '::' + if skip_index is not None: + # If we found a '::', then check if it also covers the endpoints. + parts_hi = skip_index + parts_lo = len(parts) - skip_index - 1 + if not parts[0]: + parts_hi -= 1 + if parts_hi: + msg = "Leading ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # ^: requires ^:: + if not parts[-1]: + parts_lo -= 1 + if parts_lo: + msg = "Trailing ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # :$ requires ::$ + parts_skipped = cls._HEXTET_COUNT - (parts_hi + parts_lo) + if parts_skipped < 1: + msg = "Expected at most %d other parts with '::' in %r" + raise AddressValueError(msg % (cls._HEXTET_COUNT - 1, ip_str)) + else: + # Otherwise, allocate the entire address to parts_hi. The + # endpoints could still be empty, but _parse_hextet() will check + # for that. + if len(parts) != cls._HEXTET_COUNT: + msg = "Exactly %d parts expected without '::' in %r" + raise AddressValueError(msg % (cls._HEXTET_COUNT, ip_str)) + if not parts[0]: + msg = "Leading ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # ^: requires ^:: + if not parts[-1]: + msg = "Trailing ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # :$ requires ::$ + parts_hi = len(parts) + parts_lo = 0 + parts_skipped = 0 + + try: + # Now, parse the hextets into a 128-bit integer. + ip_int = 0 + for i in range(parts_hi): + ip_int <<= 16 + ip_int |= cls._parse_hextet(parts[i]) + ip_int <<= 16 * parts_skipped + for i in range(-parts_lo, 0): + ip_int <<= 16 + ip_int |= cls._parse_hextet(parts[i]) + return ip_int + except ValueError as exc: + raise AddressValueError("%s in %r" % (exc, ip_str)) + + @classmethod + def _parse_hextet(cls, hextet_str): + """Convert an IPv6 hextet string into an integer. + + Args: + hextet_str: A string, the number to parse. + + Returns: + The hextet as an integer. + + Raises: + ValueError: if the input isn't strictly a hex number from + [0..FFFF]. + + """ + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not cls._HEX_DIGITS.issuperset(hextet_str): + raise ValueError("Only hex digits permitted in %r" % hextet_str) + # We do the length check second, since the invalid character error + # is likely to be more informative for the user + if len(hextet_str) > 4: + msg = "At most 4 characters permitted in %r" + raise ValueError(msg % hextet_str) + # Length check means we can skip checking the integer value + return int(hextet_str, 16) + + @classmethod + def _compress_hextets(cls, hextets): + """Compresses a list of hextets. + + Compresses a list of strings, replacing the longest continuous + sequence of "0" in the list with "" and adding empty strings at + the beginning or at the end of the string such that subsequently + calling ":".join(hextets) will produce the compressed version of + the IPv6 address. + + Args: + hextets: A list of strings, the hextets to compress. + + Returns: + A list of strings. + + """ + best_doublecolon_start = -1 + best_doublecolon_len = 0 + doublecolon_start = -1 + doublecolon_len = 0 + for index, hextet in enumerate(hextets): + if hextet == '0': + doublecolon_len += 1 + if doublecolon_start == -1: + # Start of a sequence of zeros. + doublecolon_start = index + if doublecolon_len > best_doublecolon_len: + # This is the longest sequence of zeros so far. + best_doublecolon_len = doublecolon_len + best_doublecolon_start = doublecolon_start + else: + doublecolon_len = 0 + doublecolon_start = -1 + + if best_doublecolon_len > 1: + best_doublecolon_end = (best_doublecolon_start + + best_doublecolon_len) + # For zeros at the end of the address. + if best_doublecolon_end == len(hextets): + hextets += [''] + hextets[best_doublecolon_start:best_doublecolon_end] = [''] + # For zeros at the beginning of the address. + if best_doublecolon_start == 0: + hextets = [''] + hextets + + return hextets + + @classmethod + def _string_from_ip_int(cls, ip_int=None): + """Turns a 128-bit integer into hexadecimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + A string, the hexadecimal representation of the address. + + Raises: + ValueError: The address is bigger than 128 bits of all ones. + + """ + if ip_int is None: + ip_int = int(cls._ip) + + if ip_int > cls._ALL_ONES: + raise ValueError('IPv6 address is too large') + + hex_str = '%032x' % ip_int + hextets = ['%x' % int(hex_str[x:x + 4], 16) for x in range(0, 32, 4)] + + hextets = cls._compress_hextets(hextets) + return ':'.join(hextets) + + def _explode_shorthand_ip_string(self): + """Expand a shortened IPv6 address. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A string, the expanded IPv6 address. + + """ + if isinstance(self, IPv6Network): + ip_str = _compat_str(self.network_address) + elif isinstance(self, IPv6Interface): + ip_str = _compat_str(self.ip) + else: + ip_str = _compat_str(self) + + ip_int = self._ip_int_from_string(ip_str) + hex_str = '%032x' % ip_int + parts = [hex_str[x:x + 4] for x in range(0, 32, 4)] + if isinstance(self, (_BaseNetwork, IPv6Interface)): + return '%s/%d' % (':'.join(parts), self._prefixlen) + return ':'.join(parts) + + def _reverse_pointer(self): + """Return the reverse DNS pointer name for the IPv6 address. + + This implements the method described in RFC3596 2.5. + + """ + reverse_chars = self.exploded[::-1].replace(':', '') + return '.'.join(reverse_chars) + '.ip6.arpa' + + @property + def max_prefixlen(self): + return self._max_prefixlen + + @property + def version(self): + return self._version + + +class IPv6Address(_BaseV6, _BaseAddress): + + """Represent and manipulate single IPv6 Addresses.""" + + __slots__ = ('_ip', '__weakref__') + + def __init__(self, address): + """Instantiate a new IPv6 address object. + + Args: + address: A string or integer representing the IP + + Additionally, an integer can be passed, so + IPv6Address('2001:db8::') == + IPv6Address(42540766411282592856903984951653826560) + or, more generally + IPv6Address(int(IPv6Address('2001:db8::'))) == + IPv6Address('2001:db8::') + + Raises: + AddressValueError: If address isn't a valid IPv6 address. + + """ + # Efficient constructor from integer. + if isinstance(address, _compat_int_types): + self._check_int_address(address) + self._ip = address + return + + # Constructing from a packed address + if isinstance(address, bytes): + self._check_packed_address(address, 16) + bvs = _compat_bytes_to_byte_vals(address) + self._ip = _compat_int_from_byte_vals(bvs, 'big') + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP string. + addr_str = _compat_str(address) + if '/' in addr_str: + raise AddressValueError("Unexpected '/' in %r" % address) + self._ip = self._ip_int_from_string(addr_str) + + @property + def packed(self): + """The binary representation of this address.""" + return v6_int_to_packed(self._ip) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is a multicast address. + See RFC 2373 2.7 for details. + + """ + return self in self._constants._multicast_network + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within one of the + reserved IPv6 Network ranges. + + """ + return any(self in x for x in self._constants._reserved_networks) + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is reserved per RFC 4291. + + """ + return self in self._constants._linklocal_network + + @property + def is_site_local(self): + """Test if the address is reserved for site-local. + + Note that the site-local address space has been deprecated by RFC 3879. + Use is_private to test if this address is in the space of unique local + addresses as defined by RFC 4193. + + Returns: + A boolean, True if the address is reserved per RFC 3513 2.5.6. + + """ + return self in self._constants._sitelocal_network + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per + iana-ipv6-special-registry. + + """ + return any(self in net for net in self._constants._private_networks) + + @property + def is_global(self): + """Test if this address is allocated for public networks. + + Returns: + A boolean, true if the address is not reserved per + iana-ipv6-special-registry. + + """ + return not self.is_private + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 2373 2.5.2. + + """ + return self._ip == 0 + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback address as defined in + RFC 2373 2.5.3. + + """ + return self._ip == 1 + + @property + def ipv4_mapped(self): + """Return the IPv4 mapped address. + + Returns: + If the IPv6 address is a v4 mapped address, return the + IPv4 mapped address. Return None otherwise. + + """ + if (self._ip >> 32) != 0xFFFF: + return None + return IPv4Address(self._ip & 0xFFFFFFFF) + + @property + def teredo(self): + """Tuple of embedded teredo IPs. + + Returns: + Tuple of the (server, client) IPs or None if the address + doesn't appear to be a teredo address (doesn't start with + 2001::/32) + + """ + if (self._ip >> 96) != 0x20010000: + return None + return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), + IPv4Address(~self._ip & 0xFFFFFFFF)) + + @property + def sixtofour(self): + """Return the IPv4 6to4 embedded address. + + Returns: + The IPv4 6to4-embedded address if present or None if the + address doesn't appear to contain a 6to4 embedded address. + + """ + if (self._ip >> 112) != 0x2002: + return None + return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) + + +class IPv6Interface(IPv6Address): + + def __init__(self, address): + if isinstance(address, (bytes, _compat_int_types)): + IPv6Address.__init__(self, address) + self.network = IPv6Network(self._ip) + self._prefixlen = self._max_prefixlen + return + if isinstance(address, tuple): + IPv6Address.__init__(self, address[0]) + if len(address) > 1: + self._prefixlen = int(address[1]) + else: + self._prefixlen = self._max_prefixlen + self.network = IPv6Network(address, strict=False) + self.netmask = self.network.netmask + self.hostmask = self.network.hostmask + return + + addr = _split_optional_netmask(address) + IPv6Address.__init__(self, addr[0]) + self.network = IPv6Network(address, strict=False) + self.netmask = self.network.netmask + self._prefixlen = self.network._prefixlen + self.hostmask = self.network.hostmask + + def __str__(self): + return '%s/%d' % (self._string_from_ip_int(self._ip), + self.network.prefixlen) + + def __eq__(self, other): + address_equal = IPv6Address.__eq__(self, other) + if not address_equal or address_equal is NotImplemented: + return address_equal + try: + return self.network == other.network + except AttributeError: + # An interface with an associated network is NOT the + # same as an unassociated address. That's why the hash + # takes the extra info into account. + return False + + def __lt__(self, other): + address_less = IPv6Address.__lt__(self, other) + if address_less is NotImplemented: + return NotImplemented + try: + return (self.network < other.network or + self.network == other.network and address_less) + except AttributeError: + # We *do* allow addresses and interfaces to be sorted. The + # unassociated address is considered less than all interfaces. + return False + + def __hash__(self): + return self._ip ^ self._prefixlen ^ int(self.network.network_address) + + __reduce__ = _IPAddressBase.__reduce__ + + @property + def ip(self): + return IPv6Address(self._ip) + + @property + def with_prefixlen(self): + return '%s/%s' % (self._string_from_ip_int(self._ip), + self._prefixlen) + + @property + def with_netmask(self): + return '%s/%s' % (self._string_from_ip_int(self._ip), + self.netmask) + + @property + def with_hostmask(self): + return '%s/%s' % (self._string_from_ip_int(self._ip), + self.hostmask) + + @property + def is_unspecified(self): + return self._ip == 0 and self.network.is_unspecified + + @property + def is_loopback(self): + return self._ip == 1 and self.network.is_loopback + + +class IPv6Network(_BaseV6, _BaseNetwork): + + """This class represents and manipulates 128-bit IPv6 networks. + + Attributes: [examples for IPv6('2001:db8::1000/124')] + .network_address: IPv6Address('2001:db8::1000') + .hostmask: IPv6Address('::f') + .broadcast_address: IPv6Address('2001:db8::100f') + .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') + .prefixlen: 124 + + """ + + # Class to use when creating address objects + _address_class = IPv6Address + + def __init__(self, address, strict=True): + """Instantiate a new IPv6 Network object. + + Args: + address: A string or integer representing the IPv6 network or the + IP and prefix/netmask. + '2001:db8::/128' + '2001:db8:0000:0000:0000:0000:0000:0000/128' + '2001:db8::' + are all functionally the same in IPv6. That is to say, + failing to provide a subnetmask will create an object with + a mask of /128. + + Additionally, an integer can be passed, so + IPv6Network('2001:db8::') == + IPv6Network(42540766411282592856903984951653826560) + or, more generally + IPv6Network(int(IPv6Network('2001:db8::'))) == + IPv6Network('2001:db8::') + + strict: A boolean. If true, ensure that we have been passed + A true network address, eg, 2001:db8::1000/124 and not an + IP address on a network, eg, 2001:db8::1/124. + + Raises: + AddressValueError: If address isn't a valid IPv6 address. + NetmaskValueError: If the netmask isn't valid for + an IPv6 address. + ValueError: If strict was True and a network address was not + supplied. + + """ + _BaseNetwork.__init__(self, address) + + # Efficient constructor from integer or packed address + if isinstance(address, (bytes, _compat_int_types)): + self.network_address = IPv6Address(address) + self.netmask, self._prefixlen = self._make_netmask( + self._max_prefixlen) + return + + if isinstance(address, tuple): + if len(address) > 1: + arg = address[1] + else: + arg = self._max_prefixlen + self.netmask, self._prefixlen = self._make_netmask(arg) + self.network_address = IPv6Address(address[0]) + packed = int(self.network_address) + if packed & int(self.netmask) != packed: + if strict: + raise ValueError('%s has host bits set' % self) + else: + self.network_address = IPv6Address(packed & + int(self.netmask)) + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = _split_optional_netmask(address) + + self.network_address = IPv6Address(self._ip_int_from_string(addr[0])) + + if len(addr) == 2: + arg = addr[1] + else: + arg = self._max_prefixlen + self.netmask, self._prefixlen = self._make_netmask(arg) + + if strict: + if (IPv6Address(int(self.network_address) & int(self.netmask)) != + self.network_address): + raise ValueError('%s has host bits set' % self) + self.network_address = IPv6Address(int(self.network_address) & + int(self.netmask)) + + if self._prefixlen == (self._max_prefixlen - 1): + self.hosts = self.__iter__ + + def hosts(self): + """Generate Iterator over usable hosts in a network. + + This is like __iter__ except it doesn't return the + Subnet-Router anycast address. + + """ + network = int(self.network_address) + broadcast = int(self.broadcast_address) + for x in _compat_range(network + 1, broadcast + 1): + yield self._address_class(x) + + @property + def is_site_local(self): + """Test if the address is reserved for site-local. + + Note that the site-local address space has been deprecated by RFC 3879. + Use is_private to test if this address is in the space of unique local + addresses as defined by RFC 4193. + + Returns: + A boolean, True if the address is reserved per RFC 3513 2.5.6. + + """ + return (self.network_address.is_site_local and + self.broadcast_address.is_site_local) + + +class _IPv6Constants(object): + + _linklocal_network = IPv6Network('fe80::/10') + + _multicast_network = IPv6Network('ff00::/8') + + _private_networks = [ + IPv6Network('::1/128'), + IPv6Network('::/128'), + IPv6Network('::ffff:0:0/96'), + IPv6Network('100::/64'), + IPv6Network('2001::/23'), + IPv6Network('2001:2::/48'), + IPv6Network('2001:db8::/32'), + IPv6Network('2001:10::/28'), + IPv6Network('fc00::/7'), + IPv6Network('fe80::/10'), + ] + + _reserved_networks = [ + IPv6Network('::/8'), IPv6Network('100::/8'), + IPv6Network('200::/7'), IPv6Network('400::/6'), + IPv6Network('800::/5'), IPv6Network('1000::/4'), + IPv6Network('4000::/3'), IPv6Network('6000::/3'), + IPv6Network('8000::/3'), IPv6Network('A000::/3'), + IPv6Network('C000::/3'), IPv6Network('E000::/4'), + IPv6Network('F000::/5'), IPv6Network('F800::/6'), + IPv6Network('FE00::/9'), + ] + + _sitelocal_network = IPv6Network('fec0::/10') + + +IPv6Address._constants = _IPv6Constants diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/__init__.py new file mode 100644 index 0000000..a6f44a5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/__init__.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- + +""" +lockfile.py - Platform-independent advisory file locks. + +Requires Python 2.5 unless you apply 2.4.diff +Locking is done on a per-thread basis instead of a per-process basis. + +Usage: + +>>> lock = LockFile('somefile') +>>> try: +... lock.acquire() +... except AlreadyLocked: +... print 'somefile', 'is locked already.' +... except LockFailed: +... print 'somefile', 'can\\'t be locked.' +... else: +... print 'got lock' +got lock +>>> print lock.is_locked() +True +>>> lock.release() + +>>> lock = LockFile('somefile') +>>> print lock.is_locked() +False +>>> with lock: +... print lock.is_locked() +True +>>> print lock.is_locked() +False + +>>> lock = LockFile('somefile') +>>> # It is okay to lock twice from the same thread... +>>> with lock: +... lock.acquire() +... +>>> # Though no counter is kept, so you can't unlock multiple times... +>>> print lock.is_locked() +False + +Exceptions: + + Error - base class for other exceptions + LockError - base class for all locking exceptions + AlreadyLocked - Another thread or process already holds the lock + LockFailed - Lock failed for some other reason + UnlockError - base class for all unlocking exceptions + AlreadyUnlocked - File was not locked. + NotMyLock - File was locked but not by the current thread/process +""" + +from __future__ import absolute_import + +import functools +import os +import socket +import threading +import warnings + +# Work with PEP8 and non-PEP8 versions of threading module. +if not hasattr(threading, "current_thread"): + threading.current_thread = threading.currentThread +if not hasattr(threading.Thread, "get_name"): + threading.Thread.get_name = threading.Thread.getName + +__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked', + 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock', + 'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock', + 'LockBase', 'locked'] + + +class Error(Exception): + """ + Base class for other exceptions. + + >>> try: + ... raise Error + ... except Exception: + ... pass + """ + pass + + +class LockError(Error): + """ + Base class for error arising from attempts to acquire the lock. + + >>> try: + ... raise LockError + ... except Error: + ... pass + """ + pass + + +class LockTimeout(LockError): + """Raised when lock creation fails within a user-defined period of time. + + >>> try: + ... raise LockTimeout + ... except LockError: + ... pass + """ + pass + + +class AlreadyLocked(LockError): + """Some other thread/process is locking the file. + + >>> try: + ... raise AlreadyLocked + ... except LockError: + ... pass + """ + pass + + +class LockFailed(LockError): + """Lock file creation failed for some other reason. + + >>> try: + ... raise LockFailed + ... except LockError: + ... pass + """ + pass + + +class UnlockError(Error): + """ + Base class for errors arising from attempts to release the lock. + + >>> try: + ... raise UnlockError + ... except Error: + ... pass + """ + pass + + +class NotLocked(UnlockError): + """Raised when an attempt is made to unlock an unlocked file. + + >>> try: + ... raise NotLocked + ... except UnlockError: + ... pass + """ + pass + + +class NotMyLock(UnlockError): + """Raised when an attempt is made to unlock a file someone else locked. + + >>> try: + ... raise NotMyLock + ... except UnlockError: + ... pass + """ + pass + + +class _SharedBase(object): + def __init__(self, path): + self.path = path + + def acquire(self, timeout=None): + """ + Acquire the lock. + + * If timeout is omitted (or None), wait forever trying to lock the + file. + + * If timeout > 0, try to acquire the lock for that many seconds. If + the lock period expires and the file is still locked, raise + LockTimeout. + + * If timeout <= 0, raise AlreadyLocked immediately if the file is + already locked. + """ + raise NotImplemented("implement in subclass") + + def release(self): + """ + Release the lock. + + If the file is not locked, raise NotLocked. + """ + raise NotImplemented("implement in subclass") + + def __enter__(self): + """ + Context manager support. + """ + self.acquire() + return self + + def __exit__(self, *_exc): + """ + Context manager support. + """ + self.release() + + def __repr__(self): + return "<%s: %r>" % (self.__class__.__name__, self.path) + + +class LockBase(_SharedBase): + """Base class for platform-specific lock classes.""" + def __init__(self, path, threaded=True, timeout=None): + """ + >>> lock = LockBase('somefile') + >>> lock = LockBase('somefile', threaded=False) + """ + super(LockBase, self).__init__(path) + self.lock_file = os.path.abspath(path) + ".lock" + self.hostname = socket.gethostname() + self.pid = os.getpid() + if threaded: + t = threading.current_thread() + # Thread objects in Python 2.4 and earlier do not have ident + # attrs. Worm around that. + ident = getattr(t, "ident", hash(t)) + self.tname = "-%x" % (ident & 0xffffffff) + else: + self.tname = "" + dirname = os.path.dirname(self.lock_file) + + # unique name is mostly about the current process, but must + # also contain the path -- otherwise, two adjacent locked + # files conflict (one file gets locked, creating lock-file and + # unique file, the other one gets locked, creating lock-file + # and overwriting the already existing lock-file, then one + # gets unlocked, deleting both lock-file and unique file, + # finally the last lock errors out upon releasing. + self.unique_name = os.path.join(dirname, + "%s%s.%s%s" % (self.hostname, + self.tname, + self.pid, + hash(self.path))) + self.timeout = timeout + + def is_locked(self): + """ + Tell whether or not the file is locked. + """ + raise NotImplemented("implement in subclass") + + def i_am_locking(self): + """ + Return True if this object is locking the file. + """ + raise NotImplemented("implement in subclass") + + def break_lock(self): + """ + Remove a lock. Useful if a locking thread failed to unlock. + """ + raise NotImplemented("implement in subclass") + + def __repr__(self): + return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name, + self.path) + + +def _fl_helper(cls, mod, *args, **kwds): + warnings.warn("Import from %s module instead of lockfile package" % mod, + DeprecationWarning, stacklevel=2) + # This is a bit funky, but it's only for awhile. The way the unit tests + # are constructed this function winds up as an unbound method, so it + # actually takes three args, not two. We want to toss out self. + if not isinstance(args[0], str): + # We are testing, avoid the first arg + args = args[1:] + if len(args) == 1 and not kwds: + kwds["threaded"] = True + return cls(*args, **kwds) + + +def LinkFileLock(*args, **kwds): + """Factory function provided for backwards compatibility. + + Do not use in new code. Instead, import LinkLockFile from the + lockfile.linklockfile module. + """ + from . import linklockfile + return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile", + *args, **kwds) + + +def MkdirFileLock(*args, **kwds): + """Factory function provided for backwards compatibility. + + Do not use in new code. Instead, import MkdirLockFile from the + lockfile.mkdirlockfile module. + """ + from . import mkdirlockfile + return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile", + *args, **kwds) + + +def SQLiteFileLock(*args, **kwds): + """Factory function provided for backwards compatibility. + + Do not use in new code. Instead, import SQLiteLockFile from the + lockfile.mkdirlockfile module. + """ + from . import sqlitelockfile + return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile", + *args, **kwds) + + +def locked(path, timeout=None): + """Decorator which enables locks for decorated function. + + Arguments: + - path: path for lockfile. + - timeout (optional): Timeout for acquiring lock. + + Usage: + @locked('/var/run/myname', timeout=0) + def myname(...): + ... + """ + def decor(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + lock = FileLock(path, timeout=timeout) + lock.acquire() + try: + return func(*args, **kwargs) + finally: + lock.release() + return wrapper + return decor + + +if hasattr(os, "link"): + from . import linklockfile as _llf + LockFile = _llf.LinkLockFile +else: + from . import mkdirlockfile as _mlf + LockFile = _mlf.MkdirLockFile + +FileLock = LockFile diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/linklockfile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/linklockfile.py new file mode 100644 index 0000000..2ca9be0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/linklockfile.py @@ -0,0 +1,73 @@ +from __future__ import absolute_import + +import time +import os + +from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, + AlreadyLocked) + + +class LinkLockFile(LockBase): + """Lock access to a file using atomic property of link(2). + + >>> lock = LinkLockFile('somefile') + >>> lock = LinkLockFile('somefile', threaded=False) + """ + + def acquire(self, timeout=None): + try: + open(self.unique_name, "wb").close() + except IOError: + raise LockFailed("failed to create %s" % self.unique_name) + + timeout = timeout if timeout is not None else self.timeout + end_time = time.time() + if timeout is not None and timeout > 0: + end_time += timeout + + while True: + # Try and create a hard link to it. + try: + os.link(self.unique_name, self.lock_file) + except OSError: + # Link creation failed. Maybe we've double-locked? + nlinks = os.stat(self.unique_name).st_nlink + if nlinks == 2: + # The original link plus the one I created == 2. We're + # good to go. + return + else: + # Otherwise the lock creation failed. + if timeout is not None and time.time() > end_time: + os.unlink(self.unique_name) + if timeout > 0: + raise LockTimeout("Timeout waiting to acquire" + " lock for %s" % + self.path) + else: + raise AlreadyLocked("%s is already locked" % + self.path) + time.sleep(timeout is not None and timeout / 10 or 0.1) + else: + # Link creation succeeded. We're good to go. + return + + def release(self): + if not self.is_locked(): + raise NotLocked("%s is not locked" % self.path) + elif not os.path.exists(self.unique_name): + raise NotMyLock("%s is locked, but not by me" % self.path) + os.unlink(self.unique_name) + os.unlink(self.lock_file) + + def is_locked(self): + return os.path.exists(self.lock_file) + + def i_am_locking(self): + return (self.is_locked() and + os.path.exists(self.unique_name) and + os.stat(self.unique_name).st_nlink == 2) + + def break_lock(self): + if os.path.exists(self.lock_file): + os.unlink(self.lock_file) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/mkdirlockfile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/mkdirlockfile.py new file mode 100644 index 0000000..05a8c96 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/mkdirlockfile.py @@ -0,0 +1,84 @@ +from __future__ import absolute_import, division + +import time +import os +import sys +import errno + +from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, + AlreadyLocked) + + +class MkdirLockFile(LockBase): + """Lock file by creating a directory.""" + def __init__(self, path, threaded=True, timeout=None): + """ + >>> lock = MkdirLockFile('somefile') + >>> lock = MkdirLockFile('somefile', threaded=False) + """ + LockBase.__init__(self, path, threaded, timeout) + # Lock file itself is a directory. Place the unique file name into + # it. + self.unique_name = os.path.join(self.lock_file, + "%s.%s%s" % (self.hostname, + self.tname, + self.pid)) + + def acquire(self, timeout=None): + timeout = timeout if timeout is not None else self.timeout + end_time = time.time() + if timeout is not None and timeout > 0: + end_time += timeout + + if timeout is None: + wait = 0.1 + else: + wait = max(0, timeout / 10) + + while True: + try: + os.mkdir(self.lock_file) + except OSError: + err = sys.exc_info()[1] + if err.errno == errno.EEXIST: + # Already locked. + if os.path.exists(self.unique_name): + # Already locked by me. + return + if timeout is not None and time.time() > end_time: + if timeout > 0: + raise LockTimeout("Timeout waiting to acquire" + " lock for %s" % + self.path) + else: + # Someone else has the lock. + raise AlreadyLocked("%s is already locked" % + self.path) + time.sleep(wait) + else: + # Couldn't create the lock for some other reason + raise LockFailed("failed to create %s" % self.lock_file) + else: + open(self.unique_name, "wb").close() + return + + def release(self): + if not self.is_locked(): + raise NotLocked("%s is not locked" % self.path) + elif not os.path.exists(self.unique_name): + raise NotMyLock("%s is locked, but not by me" % self.path) + os.unlink(self.unique_name) + os.rmdir(self.lock_file) + + def is_locked(self): + return os.path.exists(self.lock_file) + + def i_am_locking(self): + return (self.is_locked() and + os.path.exists(self.unique_name)) + + def break_lock(self): + if os.path.exists(self.lock_file): + for name in os.listdir(self.lock_file): + os.unlink(os.path.join(self.lock_file, name)) + os.rmdir(self.lock_file) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/pidlockfile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/pidlockfile.py new file mode 100644 index 0000000..069e85b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/pidlockfile.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +# pidlockfile.py +# +# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au> +# +# This is free software: you may copy, modify, and/or distribute this work +# under the terms of the Python Software Foundation License, version 2 or +# later as published by the Python Software Foundation. +# No warranty expressed or implied. See the file LICENSE.PSF-2 for details. + +""" Lockfile behaviour implemented via Unix PID files. + """ + +from __future__ import absolute_import + +import errno +import os +import time + +from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock, + LockTimeout) + + +class PIDLockFile(LockBase): + """ Lockfile implemented as a Unix PID file. + + The lock file is a normal file named by the attribute `path`. + A lock's PID file contains a single line of text, containing + the process ID (PID) of the process that acquired the lock. + + >>> lock = PIDLockFile('somefile') + >>> lock = PIDLockFile('somefile') + """ + + def __init__(self, path, threaded=False, timeout=None): + # pid lockfiles don't support threaded operation, so always force + # False as the threaded arg. + LockBase.__init__(self, path, False, timeout) + self.unique_name = self.path + + def read_pid(self): + """ Get the PID from the lock file. + """ + return read_pid_from_pidfile(self.path) + + def is_locked(self): + """ Test if the lock is currently held. + + The lock is held if the PID file for this lock exists. + + """ + return os.path.exists(self.path) + + def i_am_locking(self): + """ Test if the lock is held by the current process. + + Returns ``True`` if the current process ID matches the + number stored in the PID file. + """ + return self.is_locked() and os.getpid() == self.read_pid() + + def acquire(self, timeout=None): + """ Acquire the lock. + + Creates the PID file for this lock, or raises an error if + the lock could not be acquired. + """ + + timeout = timeout if timeout is not None else self.timeout + end_time = time.time() + if timeout is not None and timeout > 0: + end_time += timeout + + while True: + try: + write_pid_to_pidfile(self.path) + except OSError as exc: + if exc.errno == errno.EEXIST: + # The lock creation failed. Maybe sleep a bit. + if time.time() > end_time: + if timeout is not None and timeout > 0: + raise LockTimeout("Timeout waiting to acquire" + " lock for %s" % + self.path) + else: + raise AlreadyLocked("%s is already locked" % + self.path) + time.sleep(timeout is not None and timeout / 10 or 0.1) + else: + raise LockFailed("failed to create %s" % self.path) + else: + return + + def release(self): + """ Release the lock. + + Removes the PID file to release the lock, or raises an + error if the current process does not hold the lock. + + """ + if not self.is_locked(): + raise NotLocked("%s is not locked" % self.path) + if not self.i_am_locking(): + raise NotMyLock("%s is locked, but not by me" % self.path) + remove_existing_pidfile(self.path) + + def break_lock(self): + """ Break an existing lock. + + Removes the PID file if it already exists, otherwise does + nothing. + + """ + remove_existing_pidfile(self.path) + + +def read_pid_from_pidfile(pidfile_path): + """ Read the PID recorded in the named PID file. + + Read and return the numeric PID recorded as text in the named + PID file. If the PID file cannot be read, or if the content is + not a valid PID, return ``None``. + + """ + pid = None + try: + pidfile = open(pidfile_path, 'r') + except IOError: + pass + else: + # According to the FHS 2.3 section on PID files in /var/run: + # + # The file must consist of the process identifier in + # ASCII-encoded decimal, followed by a newline character. + # + # Programs that read PID files should be somewhat flexible + # in what they accept; i.e., they should ignore extra + # whitespace, leading zeroes, absence of the trailing + # newline, or additional lines in the PID file. + + line = pidfile.readline().strip() + try: + pid = int(line) + except ValueError: + pass + pidfile.close() + + return pid + + +def write_pid_to_pidfile(pidfile_path): + """ Write the PID in the named PID file. + + Get the numeric process ID (“PID”) of the current process + and write it to the named file as a line of text. + + """ + open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY) + open_mode = 0o644 + pidfile_fd = os.open(pidfile_path, open_flags, open_mode) + pidfile = os.fdopen(pidfile_fd, 'w') + + # According to the FHS 2.3 section on PID files in /var/run: + # + # The file must consist of the process identifier in + # ASCII-encoded decimal, followed by a newline character. For + # example, if crond was process number 25, /var/run/crond.pid + # would contain three characters: two, five, and newline. + + pid = os.getpid() + pidfile.write("%s\n" % pid) + pidfile.close() + + +def remove_existing_pidfile(pidfile_path): + """ Remove the named PID file if it exists. + + Removing a PID file that doesn't already exist puts us in the + desired state, so we ignore the condition if the file does not + exist. + + """ + try: + os.remove(pidfile_path) + except OSError as exc: + if exc.errno == errno.ENOENT: + pass + else: + raise diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/sqlitelockfile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/sqlitelockfile.py new file mode 100644 index 0000000..f997e24 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/sqlitelockfile.py @@ -0,0 +1,156 @@ +from __future__ import absolute_import, division + +import time +import os + +try: + unicode +except NameError: + unicode = str + +from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked + + +class SQLiteLockFile(LockBase): + "Demonstrate SQL-based locking." + + testdb = None + + def __init__(self, path, threaded=True, timeout=None): + """ + >>> lock = SQLiteLockFile('somefile') + >>> lock = SQLiteLockFile('somefile', threaded=False) + """ + LockBase.__init__(self, path, threaded, timeout) + self.lock_file = unicode(self.lock_file) + self.unique_name = unicode(self.unique_name) + + if SQLiteLockFile.testdb is None: + import tempfile + _fd, testdb = tempfile.mkstemp() + os.close(_fd) + os.unlink(testdb) + del _fd, tempfile + SQLiteLockFile.testdb = testdb + + import sqlite3 + self.connection = sqlite3.connect(SQLiteLockFile.testdb) + + c = self.connection.cursor() + try: + c.execute("create table locks" + "(" + " lock_file varchar(32)," + " unique_name varchar(32)" + ")") + except sqlite3.OperationalError: + pass + else: + self.connection.commit() + import atexit + atexit.register(os.unlink, SQLiteLockFile.testdb) + + def acquire(self, timeout=None): + timeout = timeout if timeout is not None else self.timeout + end_time = time.time() + if timeout is not None and timeout > 0: + end_time += timeout + + if timeout is None: + wait = 0.1 + elif timeout <= 0: + wait = 0 + else: + wait = timeout / 10 + + cursor = self.connection.cursor() + + while True: + if not self.is_locked(): + # Not locked. Try to lock it. + cursor.execute("insert into locks" + " (lock_file, unique_name)" + " values" + " (?, ?)", + (self.lock_file, self.unique_name)) + self.connection.commit() + + # Check to see if we are the only lock holder. + cursor.execute("select * from locks" + " where unique_name = ?", + (self.unique_name,)) + rows = cursor.fetchall() + if len(rows) > 1: + # Nope. Someone else got there. Remove our lock. + cursor.execute("delete from locks" + " where unique_name = ?", + (self.unique_name,)) + self.connection.commit() + else: + # Yup. We're done, so go home. + return + else: + # Check to see if we are the only lock holder. + cursor.execute("select * from locks" + " where unique_name = ?", + (self.unique_name,)) + rows = cursor.fetchall() + if len(rows) == 1: + # We're the locker, so go home. + return + + # Maybe we should wait a bit longer. + if timeout is not None and time.time() > end_time: + if timeout > 0: + # No more waiting. + raise LockTimeout("Timeout waiting to acquire" + " lock for %s" % + self.path) + else: + # Someone else has the lock and we are impatient.. + raise AlreadyLocked("%s is already locked" % self.path) + + # Well, okay. We'll give it a bit longer. + time.sleep(wait) + + def release(self): + if not self.is_locked(): + raise NotLocked("%s is not locked" % self.path) + if not self.i_am_locking(): + raise NotMyLock("%s is locked, but not by me (by %s)" % + (self.unique_name, self._who_is_locking())) + cursor = self.connection.cursor() + cursor.execute("delete from locks" + " where unique_name = ?", + (self.unique_name,)) + self.connection.commit() + + def _who_is_locking(self): + cursor = self.connection.cursor() + cursor.execute("select unique_name from locks" + " where lock_file = ?", + (self.lock_file,)) + return cursor.fetchone()[0] + + def is_locked(self): + cursor = self.connection.cursor() + cursor.execute("select * from locks" + " where lock_file = ?", + (self.lock_file,)) + rows = cursor.fetchall() + return not not rows + + def i_am_locking(self): + cursor = self.connection.cursor() + cursor.execute("select * from locks" + " where lock_file = ?" + " and unique_name = ?", + (self.lock_file, self.unique_name)) + return not not cursor.fetchall() + + def break_lock(self): + cursor = self.connection.cursor() + cursor.execute("delete from locks" + " where lock_file = ?", + (self.lock_file,)) + self.connection.commit() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/symlinklockfile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/symlinklockfile.py new file mode 100644 index 0000000..23b41f5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/lockfile/symlinklockfile.py @@ -0,0 +1,70 @@ +from __future__ import absolute_import + +import os +import time + +from . import (LockBase, NotLocked, NotMyLock, LockTimeout, + AlreadyLocked) + + +class SymlinkLockFile(LockBase): + """Lock access to a file using symlink(2).""" + + def __init__(self, path, threaded=True, timeout=None): + # super(SymlinkLockFile).__init(...) + LockBase.__init__(self, path, threaded, timeout) + # split it back! + self.unique_name = os.path.split(self.unique_name)[1] + + def acquire(self, timeout=None): + # Hopefully unnecessary for symlink. + # try: + # open(self.unique_name, "wb").close() + # except IOError: + # raise LockFailed("failed to create %s" % self.unique_name) + timeout = timeout if timeout is not None else self.timeout + end_time = time.time() + if timeout is not None and timeout > 0: + end_time += timeout + + while True: + # Try and create a symbolic link to it. + try: + os.symlink(self.unique_name, self.lock_file) + except OSError: + # Link creation failed. Maybe we've double-locked? + if self.i_am_locking(): + # Linked to out unique name. Proceed. + return + else: + # Otherwise the lock creation failed. + if timeout is not None and time.time() > end_time: + if timeout > 0: + raise LockTimeout("Timeout waiting to acquire" + " lock for %s" % + self.path) + else: + raise AlreadyLocked("%s is already locked" % + self.path) + time.sleep(timeout / 10 if timeout is not None else 0.1) + else: + # Link creation succeeded. We're good to go. + return + + def release(self): + if not self.is_locked(): + raise NotLocked("%s is not locked" % self.path) + elif not self.i_am_locking(): + raise NotMyLock("%s is locked, but not by me" % self.path) + os.unlink(self.lock_file) + + def is_locked(self): + return os.path.islink(self.lock_file) + + def i_am_locking(self): + return (os.path.islink(self.lock_file) + and os.readlink(self.lock_file) == self.unique_name) + + def break_lock(self): + if os.path.islink(self.lock_file): # exists && link + os.unlink(self.lock_file) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/__init__.py new file mode 100644 index 0000000..2afca5a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/__init__.py @@ -0,0 +1,66 @@ +# coding: utf-8 +from pip._vendor.msgpack._version import version +from pip._vendor.msgpack.exceptions import * + +from collections import namedtuple + + +class ExtType(namedtuple('ExtType', 'code data')): + """ExtType represents ext type in msgpack.""" + def __new__(cls, code, data): + if not isinstance(code, int): + raise TypeError("code must be int") + if not isinstance(data, bytes): + raise TypeError("data must be bytes") + if not 0 <= code <= 127: + raise ValueError("code must be 0~127") + return super(ExtType, cls).__new__(cls, code, data) + + +import os +if os.environ.get('MSGPACK_PUREPYTHON'): + from pip._vendor.msgpack.fallback import Packer, unpackb, Unpacker +else: + try: + from pip._vendor.msgpack._packer import Packer + from pip._vendor.msgpack._unpacker import unpackb, Unpacker + except ImportError: + from pip._vendor.msgpack.fallback import Packer, unpackb, Unpacker + + +def pack(o, stream, **kwargs): + """ + Pack object `o` and write it to `stream` + + See :class:`Packer` for options. + """ + packer = Packer(**kwargs) + stream.write(packer.pack(o)) + + +def packb(o, **kwargs): + """ + Pack object `o` and return packed bytes + + See :class:`Packer` for options. + """ + return Packer(**kwargs).pack(o) + + +def unpack(stream, **kwargs): + """ + Unpack an object from `stream`. + + Raises `ExtraData` when `stream` contains extra bytes. + See :class:`Unpacker` for options. + """ + data = stream.read() + return unpackb(data, **kwargs) + + +# alias for compatibility to simplejson/marshal/pickle. +load = unpack +loads = unpackb + +dump = pack +dumps = packb diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/_version.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/_version.py new file mode 100644 index 0000000..d28f0de --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/_version.py @@ -0,0 +1 @@ +version = (0, 5, 6) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/exceptions.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/exceptions.py new file mode 100644 index 0000000..9766881 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/exceptions.py @@ -0,0 +1,41 @@ +class UnpackException(Exception): + """Deprecated. Use Exception instead to catch all exception during unpacking.""" + + +class BufferFull(UnpackException): + pass + + +class OutOfData(UnpackException): + pass + + +class UnpackValueError(UnpackException, ValueError): + """Deprecated. Use ValueError instead.""" + + +class ExtraData(UnpackValueError): + def __init__(self, unpacked, extra): + self.unpacked = unpacked + self.extra = extra + + def __str__(self): + return "unpack(b) received extra data." + + +class PackException(Exception): + """Deprecated. Use Exception instead to catch all exception during packing.""" + + +class PackValueError(PackException, ValueError): + """PackValueError is raised when type of input data is supported but it's value is unsupported. + + Deprecated. Use ValueError instead. + """ + + +class PackOverflowError(PackValueError, OverflowError): + """PackOverflowError is raised when integer value is out of range of msgpack support [-2**31, 2**32). + + Deprecated. Use ValueError instead. + """ diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/fallback.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/fallback.py new file mode 100644 index 0000000..9418421 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/msgpack/fallback.py @@ -0,0 +1,977 @@ +"""Fallback pure Python implementation of msgpack""" + +import sys +import struct +import warnings + +if sys.version_info[0] == 3: + PY3 = True + int_types = int + Unicode = str + xrange = range + def dict_iteritems(d): + return d.items() +else: + PY3 = False + int_types = (int, long) + Unicode = unicode + def dict_iteritems(d): + return d.iteritems() + + +if hasattr(sys, 'pypy_version_info'): + # cStringIO is slow on PyPy, StringIO is faster. However: PyPy's own + # StringBuilder is fastest. + from __pypy__ import newlist_hint + try: + from __pypy__.builders import BytesBuilder as StringBuilder + except ImportError: + from __pypy__.builders import StringBuilder + USING_STRINGBUILDER = True + class StringIO(object): + def __init__(self, s=b''): + if s: + self.builder = StringBuilder(len(s)) + self.builder.append(s) + else: + self.builder = StringBuilder() + def write(self, s): + if isinstance(s, memoryview): + s = s.tobytes() + elif isinstance(s, bytearray): + s = bytes(s) + self.builder.append(s) + def getvalue(self): + return self.builder.build() +else: + USING_STRINGBUILDER = False + from io import BytesIO as StringIO + newlist_hint = lambda size: [] + + +from pip._vendor.msgpack.exceptions import ( + BufferFull, + OutOfData, + UnpackValueError, + PackValueError, + PackOverflowError, + ExtraData) + +from pip._vendor.msgpack import ExtType + + +EX_SKIP = 0 +EX_CONSTRUCT = 1 +EX_READ_ARRAY_HEADER = 2 +EX_READ_MAP_HEADER = 3 + +TYPE_IMMEDIATE = 0 +TYPE_ARRAY = 1 +TYPE_MAP = 2 +TYPE_RAW = 3 +TYPE_BIN = 4 +TYPE_EXT = 5 + +DEFAULT_RECURSE_LIMIT = 511 + + +def _check_type_strict(obj, t, type=type, tuple=tuple): + if type(t) is tuple: + return type(obj) in t + else: + return type(obj) is t + + +def _get_data_from_buffer(obj): + try: + view = memoryview(obj) + except TypeError: + # try to use legacy buffer protocol if 2.7, otherwise re-raise + if not PY3: + view = memoryview(buffer(obj)) + warnings.warn("using old buffer interface to unpack %s; " + "this leads to unpacking errors if slicing is used and " + "will be removed in a future version" % type(obj), + RuntimeWarning) + else: + raise + if view.itemsize != 1: + raise ValueError("cannot unpack from multi-byte object") + return view + + +def unpack(stream, **kwargs): + warnings.warn( + "Direct calling implementation's unpack() is deprecated, Use msgpack.unpack() or unpackb() instead.", + PendingDeprecationWarning) + data = stream.read() + return unpackb(data, **kwargs) + + +def unpackb(packed, **kwargs): + """ + Unpack an object from `packed`. + + Raises `ExtraData` when `packed` contains extra bytes. + See :class:`Unpacker` for options. + """ + unpacker = Unpacker(None, **kwargs) + unpacker.feed(packed) + try: + ret = unpacker._unpack() + except OutOfData: + raise UnpackValueError("Data is not enough.") + if unpacker._got_extradata(): + raise ExtraData(ret, unpacker._get_extradata()) + return ret + + +class Unpacker(object): + """Streaming unpacker. + + arguments: + + :param file_like: + File-like object having `.read(n)` method. + If specified, unpacker reads serialized data from it and :meth:`feed()` is not usable. + + :param int read_size: + Used as `file_like.read(read_size)`. (default: `min(16*1024, max_buffer_size)`) + + :param bool use_list: + If true, unpack msgpack array to Python list. + Otherwise, unpack to Python tuple. (default: True) + + :param bool raw: + If true, unpack msgpack raw to Python bytes (default). + Otherwise, unpack to Python str (or unicode on Python 2) by decoding + with UTF-8 encoding (recommended). + Currently, the default is true, but it will be changed to false in + near future. So you must specify it explicitly for keeping backward + compatibility. + + *encoding* option which is deprecated overrides this option. + + :param callable object_hook: + When specified, it should be callable. + Unpacker calls it with a dict argument after unpacking msgpack map. + (See also simplejson) + + :param callable object_pairs_hook: + When specified, it should be callable. + Unpacker calls it with a list of key-value pairs after unpacking msgpack map. + (See also simplejson) + + :param str encoding: + Encoding used for decoding msgpack raw. + If it is None (default), msgpack raw is deserialized to Python bytes. + + :param str unicode_errors: + (deprecated) Used for decoding msgpack raw with *encoding*. + (default: `'strict'`) + + :param int max_buffer_size: + Limits size of data waiting unpacked. 0 means system's INT_MAX (default). + Raises `BufferFull` exception when it is insufficient. + You should set this parameter when unpacking data from untrusted source. + + :param int max_str_len: + Limits max length of str. (default: 2**31-1) + + :param int max_bin_len: + Limits max length of bin. (default: 2**31-1) + + :param int max_array_len: + Limits max length of array. (default: 2**31-1) + + :param int max_map_len: + Limits max length of map. (default: 2**31-1) + + + example of streaming deserialize from file-like object:: + + unpacker = Unpacker(file_like, raw=False) + for o in unpacker: + process(o) + + example of streaming deserialize from socket:: + + unpacker = Unpacker(raw=False) + while True: + buf = sock.recv(1024**2) + if not buf: + break + unpacker.feed(buf) + for o in unpacker: + process(o) + """ + + def __init__(self, file_like=None, read_size=0, use_list=True, raw=True, + object_hook=None, object_pairs_hook=None, list_hook=None, + encoding=None, unicode_errors=None, max_buffer_size=0, + ext_hook=ExtType, + max_str_len=2147483647, # 2**32-1 + max_bin_len=2147483647, + max_array_len=2147483647, + max_map_len=2147483647, + max_ext_len=2147483647): + + if encoding is not None: + warnings.warn( + "encoding is deprecated, Use raw=False instead.", + PendingDeprecationWarning) + + if unicode_errors is None: + unicode_errors = 'strict' + + if file_like is None: + self._feeding = True + else: + if not callable(file_like.read): + raise TypeError("`file_like.read` must be callable") + self.file_like = file_like + self._feeding = False + + #: array of bytes fed. + self._buffer = bytearray() + # Some very old pythons don't support `struct.unpack_from()` with a + # `bytearray`. So we wrap it in a `buffer()` there. + if sys.version_info < (2, 7, 6): + self._buffer_view = buffer(self._buffer) + else: + self._buffer_view = self._buffer + #: Which position we currently reads + self._buff_i = 0 + + # When Unpacker is used as an iterable, between the calls to next(), + # the buffer is not "consumed" completely, for efficiency sake. + # Instead, it is done sloppily. To make sure we raise BufferFull at + # the correct moments, we have to keep track of how sloppy we were. + # Furthermore, when the buffer is incomplete (that is: in the case + # we raise an OutOfData) we need to rollback the buffer to the correct + # state, which _buf_checkpoint records. + self._buf_checkpoint = 0 + + self._max_buffer_size = max_buffer_size or 2**31-1 + if read_size > self._max_buffer_size: + raise ValueError("read_size must be smaller than max_buffer_size") + self._read_size = read_size or min(self._max_buffer_size, 16*1024) + self._raw = bool(raw) + self._encoding = encoding + self._unicode_errors = unicode_errors + self._use_list = use_list + self._list_hook = list_hook + self._object_hook = object_hook + self._object_pairs_hook = object_pairs_hook + self._ext_hook = ext_hook + self._max_str_len = max_str_len + self._max_bin_len = max_bin_len + self._max_array_len = max_array_len + self._max_map_len = max_map_len + self._max_ext_len = max_ext_len + self._stream_offset = 0 + + if list_hook is not None and not callable(list_hook): + raise TypeError('`list_hook` is not callable') + if object_hook is not None and not callable(object_hook): + raise TypeError('`object_hook` is not callable') + if object_pairs_hook is not None and not callable(object_pairs_hook): + raise TypeError('`object_pairs_hook` is not callable') + if object_hook is not None and object_pairs_hook is not None: + raise TypeError("object_pairs_hook and object_hook are mutually " + "exclusive") + if not callable(ext_hook): + raise TypeError("`ext_hook` is not callable") + + def feed(self, next_bytes): + assert self._feeding + view = _get_data_from_buffer(next_bytes) + if (len(self._buffer) - self._buff_i + len(view) > self._max_buffer_size): + raise BufferFull + + # Strip buffer before checkpoint before reading file. + if self._buf_checkpoint > 0: + del self._buffer[:self._buf_checkpoint] + self._buff_i -= self._buf_checkpoint + self._buf_checkpoint = 0 + + self._buffer += view + + def _consume(self): + """ Gets rid of the used parts of the buffer. """ + self._stream_offset += self._buff_i - self._buf_checkpoint + self._buf_checkpoint = self._buff_i + + def _got_extradata(self): + return self._buff_i < len(self._buffer) + + def _get_extradata(self): + return self._buffer[self._buff_i:] + + def read_bytes(self, n): + return self._read(n) + + def _read(self, n): + # (int) -> bytearray + self._reserve(n) + i = self._buff_i + self._buff_i = i+n + return self._buffer[i:i+n] + + def _reserve(self, n): + remain_bytes = len(self._buffer) - self._buff_i - n + + # Fast path: buffer has n bytes already + if remain_bytes >= 0: + return + + if self._feeding: + self._buff_i = self._buf_checkpoint + raise OutOfData + + # Strip buffer before checkpoint before reading file. + if self._buf_checkpoint > 0: + del self._buffer[:self._buf_checkpoint] + self._buff_i -= self._buf_checkpoint + self._buf_checkpoint = 0 + + # Read from file + remain_bytes = -remain_bytes + while remain_bytes > 0: + to_read_bytes = max(self._read_size, remain_bytes) + read_data = self.file_like.read(to_read_bytes) + if not read_data: + break + assert isinstance(read_data, bytes) + self._buffer += read_data + remain_bytes -= len(read_data) + + if len(self._buffer) < n + self._buff_i: + self._buff_i = 0 # rollback + raise OutOfData + + def _read_header(self, execute=EX_CONSTRUCT): + typ = TYPE_IMMEDIATE + n = 0 + obj = None + self._reserve(1) + b = self._buffer[self._buff_i] + self._buff_i += 1 + if b & 0b10000000 == 0: + obj = b + elif b & 0b11100000 == 0b11100000: + obj = -1 - (b ^ 0xff) + elif b & 0b11100000 == 0b10100000: + n = b & 0b00011111 + typ = TYPE_RAW + if n > self._max_str_len: + raise UnpackValueError("%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 UnpackValueError("%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 UnpackValueError("%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 UnpackValueError("%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 = struct.unpack_from(">H", self._buffer_view, self._buff_i)[0] + self._buff_i += 2 + if n > self._max_bin_len: + raise UnpackValueError("%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 = struct.unpack_from(">I", self._buffer_view, self._buff_i)[0] + self._buff_i += 4 + if n > self._max_bin_len: + raise UnpackValueError("%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 = struct.unpack_from('Bb', self._buffer_view, self._buff_i) + self._buff_i += 2 + if L > self._max_ext_len: + raise UnpackValueError("%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 = struct.unpack_from('>Hb', self._buffer_view, self._buff_i) + self._buff_i += 3 + if L > self._max_ext_len: + raise UnpackValueError("%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 = struct.unpack_from('>Ib', self._buffer_view, self._buff_i) + self._buff_i += 5 + if L > self._max_ext_len: + raise UnpackValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) + obj = self._read(L) + elif b == 0xca: + self._reserve(4) + obj = struct.unpack_from(">f", self._buffer_view, self._buff_i)[0] + self._buff_i += 4 + elif b == 0xcb: + self._reserve(8) + obj = struct.unpack_from(">d", self._buffer_view, 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 = struct.unpack_from(">H", self._buffer_view, self._buff_i)[0] + self._buff_i += 2 + elif b == 0xce: + self._reserve(4) + obj = struct.unpack_from(">I", self._buffer_view, self._buff_i)[0] + self._buff_i += 4 + elif b == 0xcf: + self._reserve(8) + obj = struct.unpack_from(">Q", self._buffer_view, self._buff_i)[0] + self._buff_i += 8 + elif b == 0xd0: + self._reserve(1) + obj = struct.unpack_from("b", self._buffer_view, self._buff_i)[0] + self._buff_i += 1 + elif b == 0xd1: + self._reserve(2) + obj = struct.unpack_from(">h", self._buffer_view, self._buff_i)[0] + self._buff_i += 2 + elif b == 0xd2: + self._reserve(4) + obj = struct.unpack_from(">i", self._buffer_view, self._buff_i)[0] + self._buff_i += 4 + elif b == 0xd3: + self._reserve(8) + obj = struct.unpack_from(">q", self._buffer_view, self._buff_i)[0] + self._buff_i += 8 + elif b == 0xd4: # fixext 1 + typ = TYPE_EXT + if self._max_ext_len < 1: + raise UnpackValueError("%s exceeds max_ext_len(%s)" % (1, self._max_ext_len)) + self._reserve(2) + n, obj = struct.unpack_from("b1s", self._buffer_view, self._buff_i) + self._buff_i += 2 + elif b == 0xd5: # fixext 2 + typ = TYPE_EXT + if self._max_ext_len < 2: + raise UnpackValueError("%s exceeds max_ext_len(%s)" % (2, self._max_ext_len)) + self._reserve(3) + n, obj = struct.unpack_from("b2s", self._buffer_view, self._buff_i) + self._buff_i += 3 + elif b == 0xd6: # fixext 4 + typ = TYPE_EXT + if self._max_ext_len < 4: + raise UnpackValueError("%s exceeds max_ext_len(%s)" % (4, self._max_ext_len)) + self._reserve(5) + n, obj = struct.unpack_from("b4s", self._buffer_view, self._buff_i) + self._buff_i += 5 + elif b == 0xd7: # fixext 8 + typ = TYPE_EXT + if self._max_ext_len < 8: + raise UnpackValueError("%s exceeds max_ext_len(%s)" % (8, self._max_ext_len)) + self._reserve(9) + n, obj = struct.unpack_from("b8s", self._buffer_view, self._buff_i) + self._buff_i += 9 + elif b == 0xd8: # fixext 16 + typ = TYPE_EXT + if self._max_ext_len < 16: + raise UnpackValueError("%s exceeds max_ext_len(%s)" % (16, self._max_ext_len)) + self._reserve(17) + n, obj = struct.unpack_from("b16s", self._buffer_view, 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 UnpackValueError("%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, = struct.unpack_from(">H", self._buffer_view, self._buff_i) + self._buff_i += 2 + if n > self._max_str_len: + raise UnpackValueError("%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, = struct.unpack_from(">I", self._buffer_view, self._buff_i) + self._buff_i += 4 + if n > self._max_str_len: + raise UnpackValueError("%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, = struct.unpack_from(">H", self._buffer_view, self._buff_i) + self._buff_i += 2 + if n > self._max_array_len: + raise UnpackValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) + elif b == 0xdd: + typ = TYPE_ARRAY + self._reserve(4) + n, = struct.unpack_from(">I", self._buffer_view, self._buff_i) + self._buff_i += 4 + if n > self._max_array_len: + raise UnpackValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) + elif b == 0xde: + self._reserve(2) + n, = struct.unpack_from(">H", self._buffer_view, self._buff_i) + self._buff_i += 2 + if n > self._max_map_len: + raise UnpackValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) + typ = TYPE_MAP + elif b == 0xdf: + self._reserve(4) + n, = struct.unpack_from(">I", self._buffer_view, self._buff_i) + self._buff_i += 4 + if n > self._max_map_len: + raise UnpackValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) + typ = TYPE_MAP + else: + raise UnpackValueError("Unknown header: 0x%x" % b) + return typ, n, obj + + def _unpack(self, execute=EX_CONSTRUCT): + typ, n, obj = self._read_header(execute) + + if execute == EX_READ_ARRAY_HEADER: + if typ != TYPE_ARRAY: + raise UnpackValueError("Expected array") + return n + if execute == EX_READ_MAP_HEADER: + if typ != TYPE_MAP: + raise UnpackValueError("Expected map") + return n + # TODO should we eliminate the recursion? + if typ == TYPE_ARRAY: + if execute == EX_SKIP: + for i in xrange(n): + # TODO check whether we need to call `list_hook` + self._unpack(EX_SKIP) + return + ret = newlist_hint(n) + for i in xrange(n): + ret.append(self._unpack(EX_CONSTRUCT)) + if self._list_hook is not None: + ret = self._list_hook(ret) + # TODO is the interaction between `list_hook` and `use_list` ok? + return ret if self._use_list else tuple(ret) + if typ == TYPE_MAP: + if execute == EX_SKIP: + for i in xrange(n): + # TODO check whether we need to call hooks + self._unpack(EX_SKIP) + self._unpack(EX_SKIP) + return + if self._object_pairs_hook is not None: + ret = self._object_pairs_hook( + (self._unpack(EX_CONSTRUCT), + self._unpack(EX_CONSTRUCT)) + for _ in xrange(n)) + else: + ret = {} + for _ in xrange(n): + key = self._unpack(EX_CONSTRUCT) + ret[key] = self._unpack(EX_CONSTRUCT) + if self._object_hook is not None: + ret = self._object_hook(ret) + return ret + if execute == EX_SKIP: + return + if typ == TYPE_RAW: + if self._encoding is not None: + obj = obj.decode(self._encoding, self._unicode_errors) + elif self._raw: + obj = bytes(obj) + else: + obj = obj.decode('utf_8') + return obj + if typ == TYPE_EXT: + return self._ext_hook(n, bytes(obj)) + if typ == TYPE_BIN: + return bytes(obj) + assert typ == TYPE_IMMEDIATE + return obj + + def __iter__(self): + return self + + def __next__(self): + try: + ret = self._unpack(EX_CONSTRUCT) + self._consume() + return ret + except OutOfData: + self._consume() + raise StopIteration + + next = __next__ + + def skip(self, write_bytes=None): + self._unpack(EX_SKIP) + if write_bytes is not None: + warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) + write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) + self._consume() + + def unpack(self, write_bytes=None): + ret = self._unpack(EX_CONSTRUCT) + if write_bytes is not None: + warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) + write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) + self._consume() + return ret + + def read_array_header(self, write_bytes=None): + ret = self._unpack(EX_READ_ARRAY_HEADER) + if write_bytes is not None: + warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) + write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) + self._consume() + return ret + + def read_map_header(self, write_bytes=None): + ret = self._unpack(EX_READ_MAP_HEADER) + if write_bytes is not None: + warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) + write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) + self._consume() + return ret + + def tell(self): + return self._stream_offset + + +class Packer(object): + """ + MessagePack Packer + + usage: + + packer = Packer() + astream.write(packer.pack(a)) + astream.write(packer.pack(b)) + + Packer's constructor has some keyword arguments: + + :param callable default: + Convert user type to builtin type that Packer supports. + See also simplejson's document. + + :param bool use_single_float: + Use single precision float type for float. (default: False) + + :param bool autoreset: + Reset buffer after each pack and return its content as `bytes`. (default: True). + If set this to false, use `bytes()` to get content and `.reset()` to clear buffer. + + :param bool use_bin_type: + Use bin type introduced in msgpack spec 2.0 for bytes. + It also enables str8 type for unicode. + + :param bool strict_types: + If set to true, types will be checked to be exact. Derived classes + from serializeable types will not be serialized and will be + treated as unsupported type and forwarded to default. + Additionally tuples will not be serialized as lists. + This is useful when trying to implement accurate serialization + for python types. + + :param str encoding: + (deprecated) Convert unicode to bytes with this encoding. (default: 'utf-8') + + :param str unicode_errors: + Error handler for encoding unicode. (default: 'strict') + """ + def __init__(self, default=None, encoding=None, unicode_errors=None, + use_single_float=False, autoreset=True, use_bin_type=False, + strict_types=False): + if encoding is None: + encoding = 'utf_8' + else: + warnings.warn( + "encoding is deprecated, Use raw=False instead.", + PendingDeprecationWarning) + + if unicode_errors is None: + unicode_errors = 'strict' + + self._strict_types = strict_types + self._use_float = use_single_float + self._autoreset = autoreset + self._use_bin_type = use_bin_type + self._encoding = encoding + self._unicode_errors = unicode_errors + self._buffer = StringIO() + if default is not None: + if not callable(default): + raise TypeError("default must be callable") + self._default = default + + def _pack(self, obj, nest_limit=DEFAULT_RECURSE_LIMIT, + check=isinstance, check_type_strict=_check_type_strict): + default_used = False + if self._strict_types: + check = check_type_strict + list_types = list + else: + list_types = (list, tuple) + while True: + if nest_limit < 0: + raise PackValueError("recursion limit exceeded") + if obj is None: + return self._buffer.write(b"\xc0") + if check(obj, bool): + if obj: + return self._buffer.write(b"\xc3") + return self._buffer.write(b"\xc2") + if check(obj, int_types): + if 0 <= obj < 0x80: + return self._buffer.write(struct.pack("B", obj)) + if -0x20 <= obj < 0: + return self._buffer.write(struct.pack("b", obj)) + if 0x80 <= obj <= 0xff: + return self._buffer.write(struct.pack("BB", 0xcc, obj)) + if -0x80 <= obj < 0: + return self._buffer.write(struct.pack(">Bb", 0xd0, obj)) + if 0xff < obj <= 0xffff: + return self._buffer.write(struct.pack(">BH", 0xcd, obj)) + if -0x8000 <= obj < -0x80: + return self._buffer.write(struct.pack(">Bh", 0xd1, obj)) + if 0xffff < obj <= 0xffffffff: + return self._buffer.write(struct.pack(">BI", 0xce, obj)) + if -0x80000000 <= obj < -0x8000: + return self._buffer.write(struct.pack(">Bi", 0xd2, obj)) + if 0xffffffff < obj <= 0xffffffffffffffff: + return self._buffer.write(struct.pack(">BQ", 0xcf, obj)) + if -0x8000000000000000 <= obj < -0x80000000: + return self._buffer.write(struct.pack(">Bq", 0xd3, obj)) + if not default_used and self._default is not None: + obj = self._default(obj) + default_used = True + continue + raise PackOverflowError("Integer value out of range") + if check(obj, (bytes, bytearray)): + n = len(obj) + if n >= 2**32: + raise PackValueError("%s is too large" % type(obj).__name__) + self._pack_bin_header(n) + return self._buffer.write(obj) + if check(obj, Unicode): + if self._encoding is None: + raise TypeError( + "Can't encode unicode string: " + "no encoding is specified") + obj = obj.encode(self._encoding, self._unicode_errors) + n = len(obj) + if n >= 2**32: + raise PackValueError("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: + raise PackValueError("Memoryview is too large") + self._pack_bin_header(n) + return self._buffer.write(obj) + if check(obj, float): + if self._use_float: + return self._buffer.write(struct.pack(">Bf", 0xca, obj)) + return self._buffer.write(struct.pack(">Bd", 0xcb, obj)) + if check(obj, ExtType): + code = obj.code + data = obj.data + assert isinstance(code, int) + assert isinstance(data, bytes) + L = len(data) + if L == 1: + self._buffer.write(b'\xd4') + elif L == 2: + self._buffer.write(b'\xd5') + elif L == 4: + self._buffer.write(b'\xd6') + elif L == 8: + self._buffer.write(b'\xd7') + elif L == 16: + self._buffer.write(b'\xd8') + elif L <= 0xff: + self._buffer.write(struct.pack(">BB", 0xc7, L)) + elif L <= 0xffff: + self._buffer.write(struct.pack(">BH", 0xc8, L)) + else: + self._buffer.write(struct.pack(">BI", 0xc9, L)) + self._buffer.write(struct.pack("b", code)) + self._buffer.write(data) + return + if check(obj, list_types): + n = len(obj) + self._pack_array_header(n) + for i in xrange(n): + self._pack(obj[i], nest_limit - 1) + return + if check(obj, dict): + return self._pack_map_pairs(len(obj), dict_iteritems(obj), + nest_limit - 1) + if not default_used and self._default is not None: + obj = self._default(obj) + default_used = 1 + continue + raise TypeError("Cannot serialize %r" % (obj, )) + + def pack(self, obj): + try: + self._pack(obj) + except: + self._buffer = StringIO() # force reset + raise + ret = self._buffer.getvalue() + if self._autoreset: + self._buffer = StringIO() + elif USING_STRINGBUILDER: + self._buffer = StringIO(ret) + return ret + + def pack_map_pairs(self, pairs): + self._pack_map_pairs(len(pairs), pairs) + ret = self._buffer.getvalue() + if self._autoreset: + self._buffer = StringIO() + elif USING_STRINGBUILDER: + self._buffer = StringIO(ret) + return ret + + def pack_array_header(self, n): + if n >= 2**32: + raise PackValueError + self._pack_array_header(n) + ret = self._buffer.getvalue() + if self._autoreset: + self._buffer = StringIO() + elif USING_STRINGBUILDER: + self._buffer = StringIO(ret) + return ret + + def pack_map_header(self, n): + if n >= 2**32: + raise PackValueError + self._pack_map_header(n) + ret = self._buffer.getvalue() + if self._autoreset: + self._buffer = StringIO() + elif USING_STRINGBUILDER: + self._buffer = StringIO(ret) + return ret + + def pack_ext_type(self, typecode, data): + if not isinstance(typecode, int): + raise TypeError("typecode must have int type.") + if not 0 <= typecode <= 127: + raise ValueError("typecode should be 0-127") + if not isinstance(data, bytes): + raise TypeError("data must have bytes type") + L = len(data) + if L > 0xffffffff: + raise PackValueError("Too large data") + if L == 1: + self._buffer.write(b'\xd4') + elif L == 2: + self._buffer.write(b'\xd5') + elif L == 4: + self._buffer.write(b'\xd6') + elif L == 8: + self._buffer.write(b'\xd7') + elif L == 16: + self._buffer.write(b'\xd8') + elif L <= 0xff: + self._buffer.write(b'\xc7' + struct.pack('B', L)) + elif L <= 0xffff: + self._buffer.write(b'\xc8' + struct.pack('>H', L)) + else: + self._buffer.write(b'\xc9' + struct.pack('>I', L)) + self._buffer.write(struct.pack('B', typecode)) + self._buffer.write(data) + + def _pack_array_header(self, n): + if n <= 0x0f: + return self._buffer.write(struct.pack('B', 0x90 + n)) + if n <= 0xffff: + return self._buffer.write(struct.pack(">BH", 0xdc, n)) + if n <= 0xffffffff: + return self._buffer.write(struct.pack(">BI", 0xdd, n)) + raise PackValueError("Array is too large") + + def _pack_map_header(self, n): + if n <= 0x0f: + return self._buffer.write(struct.pack('B', 0x80 + n)) + if n <= 0xffff: + return self._buffer.write(struct.pack(">BH", 0xde, n)) + if n <= 0xffffffff: + return self._buffer.write(struct.pack(">BI", 0xdf, n)) + raise PackValueError("Dict is too large") + + def _pack_map_pairs(self, n, pairs, nest_limit=DEFAULT_RECURSE_LIMIT): + self._pack_map_header(n) + for (k, v) in pairs: + self._pack(k, nest_limit - 1) + self._pack(v, nest_limit - 1) + + def _pack_raw_header(self, n): + if n <= 0x1f: + self._buffer.write(struct.pack('B', 0xa0 + n)) + elif self._use_bin_type and n <= 0xff: + self._buffer.write(struct.pack('>BB', 0xd9, n)) + elif n <= 0xffff: + self._buffer.write(struct.pack(">BH", 0xda, n)) + elif n <= 0xffffffff: + self._buffer.write(struct.pack(">BI", 0xdb, n)) + else: + raise PackValueError('Raw is too large') + + def _pack_bin_header(self, n): + if not self._use_bin_type: + return self._pack_raw_header(n) + elif n <= 0xff: + return self._buffer.write(struct.pack('>BB', 0xc4, n)) + elif n <= 0xffff: + return self._buffer.write(struct.pack(">BH", 0xc5, n)) + elif n <= 0xffffffff: + return self._buffer.write(struct.pack(">BI", 0xc6, n)) + else: + raise PackValueError('Bin is too large') + + def bytes(self): + return self._buffer.getvalue() + + def reset(self): + self._buffer = StringIO() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__about__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__about__.py new file mode 100644 index 0000000..7481c9e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__about__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "19.0" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD or Apache License, Version 2.0" +__copyright__ = "Copyright 2014-2019 %s" % __author__ diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__init__.py new file mode 100644 index 0000000..a0cf67d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/__init__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_compat.py new file mode 100644 index 0000000..25da473 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_compat.py @@ -0,0 +1,31 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import sys + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +# flake8: noqa + +if PY3: + string_types = (str,) +else: + string_types = (basestring,) + + +def with_metaclass(meta, *bases): + """ + Create a base class with a metaclass. + """ + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_structures.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_structures.py new file mode 100644 index 0000000..68dcca6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/_structures.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + + +class Infinity(object): + def __repr__(self): + return "Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return True + + def __ge__(self, other): + return True + + def __neg__(self): + return NegativeInfinity + + +Infinity = Infinity() + + +class NegativeInfinity(object): + def __repr__(self): + return "-Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return True + + def __le__(self, other): + return True + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return False + + def __ge__(self, other): + return False + + def __neg__(self): + return Infinity + + +NegativeInfinity = NegativeInfinity() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/markers.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/markers.py new file mode 100644 index 0000000..5482476 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/markers.py @@ -0,0 +1,296 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import operator +import os +import platform +import sys + +from pip._vendor.pyparsing import ParseException, ParseResults, stringStart, stringEnd +from pip._vendor.pyparsing import ZeroOrMore, Group, Forward, QuotedString +from pip._vendor.pyparsing import Literal as L # noqa + +from ._compat import string_types +from .specifiers import Specifier, InvalidSpecifier + + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node(object): + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + def __repr__(self): + return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + + def serialize(self): + raise NotImplementedError + + +class Variable(Node): + def serialize(self): + return str(self) + + +class Value(Node): + def serialize(self): + return '"{0}"'.format(self) + + +class Op(Node): + def serialize(self): + return str(self) + + +VARIABLE = ( + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # PEP-345 + | L("extra") # undocumented setuptools legacy +) +ALIASES = { + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results): + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker(marker, first=True): + assert isinstance(marker, (list, tuple, string_types)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs, op, rhs): + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison( + "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) + ) + + return oper(lhs, rhs) + + +_undefined = object() + + +def _get_env(environment, name): + value = environment.get(name, _undefined) + + if value is _undefined: + raise UndefinedEnvironmentName( + "{0!r} does not exist in evaluation environment.".format(name) + ) + + return value + + +def _evaluate_markers(markers, environment): + groups = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, string_types)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info): + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment(): + if hasattr(sys, "implementation"): + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + else: + iver = "0" + implementation_name = "" + + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": platform.python_version()[:3], + "sys_platform": sys.platform, + } + + +class Marker(object): + def __init__(self, marker): + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( + marker, marker[e.loc : e.loc + 8] + ) + raise InvalidMarker(err_str) + + def __str__(self): + return _format_marker(self._markers) + + def __repr__(self): + return "<Marker({0!r})>".format(str(self)) + + def evaluate(self, environment=None): + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/requirements.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/requirements.py new file mode 100644 index 0000000..dbc5f11 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/requirements.py @@ -0,0 +1,138 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import string +import re + +from pip._vendor.pyparsing import stringStart, stringEnd, originalTextFor, ParseException +from pip._vendor.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine +from pip._vendor.pyparsing import Literal as L # noqa +from pip._vendor.six.moves.urllib import parse as urlparse + +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r"[^ ]+")("url") +URL = AT + URI + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") +_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start : t._original_end]) +) +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") + + +class Requirement(object): + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string): + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + 'Parse error at "{0!r}": {1}'.format( + requirement_string[e.loc : e.loc + 8], e.msg + ) + ) + + self.name = req.name + if req.url: + parsed_url = urlparse.urlparse(req.url) + if parsed_url.scheme == "file": + if urlparse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement("Invalid URL: {0}".format(req.url)) + self.url = req.url + else: + self.url = None + self.extras = set(req.extras.asList() if req.extras else []) + self.specifier = SpecifierSet(req.specifier) + self.marker = req.marker if req.marker else None + + def __str__(self): + parts = [self.name] + + if self.extras: + parts.append("[{0}]".format(",".join(sorted(self.extras)))) + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append("@ {0}".format(self.url)) + if self.marker: + parts.append(" ") + + if self.marker: + parts.append("; {0}".format(self.marker)) + + return "".join(parts) + + def __repr__(self): + return "<Requirement({0!r})>".format(str(self)) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/specifiers.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/specifiers.py new file mode 100644 index 0000000..743576a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/specifiers.py @@ -0,0 +1,749 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import abc +import functools +import itertools +import re + +from ._compat import string_types, with_metaclass +from .version import Version, LegacyVersion, parse + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): + @abc.abstractmethod + def __str__(self): + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self): + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self): + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value): + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item, prereleases=None): + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter(self, iterable, prereleases=None): + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators = {} + + def __init__(self, spec="", prereleases=None): + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + + self._spec = (match.group("operator").strip(), match.group("version").strip()) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + + def __str__(self): + return "{0}{1}".format(*self._spec) + + def __hash__(self): + return hash(self._spec) + + def __eq__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec == other._spec + + def __ne__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op): + return getattr(self, "_compare_{0}".format(self._operators[op])) + + def _coerce_version(self, version): + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self): + return self._spec[0] + + @property + def version(self): + return self._spec[1] + + @property + def prereleases(self): + return self._prereleases + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + return self._get_operator(self.operator)(item, self.version) + + def filter(self, iterable, prereleases=None): + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later incase nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = r""" + (?P<operator>(==|!=|<=|>=|<|>)) + \s* + (?P<version> + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def _coerce_version(self, version): + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective, spec): + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective, spec): + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective, spec): + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective, spec): + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective, spec): + return prospective > self._coerce_version(spec) + + +def _require_version_compare(fn): + @functools.wraps(fn) + def wrapped(self, prospective, spec): + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = r""" + (?P<operator>(~=|==|!=|<=|>=|<|>|===)) + (?P<version> + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?<!==|!=|~=) # We have special cases for these + # operators so we want to make sure they + # don't match here. + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "~=": "compatible", + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective, spec): + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore post and dev releases and we want to treat the pre-release as + # it's own separate segment. + prefix = ".".join( + list( + itertools.takewhile( + lambda x: (not x.startswith("post") and not x.startswith("dev")), + _version_split(spec), + ) + )[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + @_require_version_compare + def _compare_equal(self, prospective, spec): + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + prospective = prospective[: len(spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + spec, prospective = _pad_version(spec, prospective) + else: + # Convert our spec string into a Version + spec = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec.local: + prospective = Version(prospective.public) + + return prospective == spec + + @_require_version_compare + def _compare_not_equal(self, prospective, spec): + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective, spec): + return prospective <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective, spec): + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self): + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version): + result = [] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _pad_version(left, right): + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + + +class SpecifierSet(BaseSpecifier): + def __init__(self, specifiers="", prereleases=None): + # Split on , to break each indidivual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed = set() + for specifier in specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<SpecifierSet({0!r}{1})>".format(str(self), pre) + + def __str__(self): + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self): + return hash(self._specs) + + def __and__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self): + return len(self._specs) + + def __iter__(self): + return iter(self._specs) + + @property + def prereleases(self): + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter(self, iterable, prereleases=None): + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered = [] + found_prereleases = [] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/utils.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/utils.py new file mode 100644 index 0000000..8841878 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/utils.py @@ -0,0 +1,57 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import re + +from .version import InvalidVersion, Version + + +_canonicalize_regex = re.compile(r"[-_.]+") + + +def canonicalize_name(name): + # This is taken from PEP 503. + return _canonicalize_regex.sub("-", name).lower() + + +def canonicalize_version(version): + """ + This is very similar to Version.__str__, but has one subtle differences + with the way it handles the release segment. + """ + + try: + version = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + + parts = [] + + # Epoch + if version.epoch != 0: + parts.append("{0}!".format(version.epoch)) + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + + # Pre-release + if version.pre is not None: + parts.append("".join(str(x) for x in version.pre)) + + # Post-release + if version.post is not None: + parts.append(".post{0}".format(version.post)) + + # Development release + if version.dev is not None: + parts.append(".dev{0}".format(version.dev)) + + # Local version segment + if version.local is not None: + parts.append("+{0}".format(version.local)) + + return "".join(parts) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/version.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/version.py new file mode 100644 index 0000000..95157a1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/packaging/version.py @@ -0,0 +1,420 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import collections +import itertools +import re + +from ._structures import Infinity + + +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] + + +_Version = collections.namedtuple( + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] +) + + +def parse(version): + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion(object): + def __hash__(self): + return hash(self._key) + + def __lt__(self, other): + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + def __init__(self, version): + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + return self._version + + def __repr__(self): + return "<LegacyVersion({0})>".format(repr(str(self))) + + @property + def public(self): + return self._version + + @property + def base_version(self): + return self._version + + @property + def epoch(self): + return -1 + + @property + def release(self): + return None + + @property + def pre(self): + return None + + @property + def post(self): + return None + + @property + def dev(self): + return None + + @property + def local(self): + return None + + @property + def is_prerelease(self): + return False + + @property + def is_postrelease(self): + return False + + @property + def is_devrelease(self): + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s): + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + parts = tuple(parts) + + return epoch, parts + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P<epoch>[0-9]+)!)? # epoch + (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment + (?P<pre> # pre-release + [-_\.]? + (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) + [-_\.]? + (?P<pre_n>[0-9]+)? + )? + (?P<post> # post release + (?:-(?P<post_n1>[0-9]+)) + | + (?: + [-_\.]? + (?P<post_l>post|rev|r) + [-_\.]? + (?P<post_n2>[0-9]+)? + ) + )? + (?P<dev> # dev release + [-_\.]? + (?P<dev_l>dev) + [-_\.]? + (?P<dev_n>[0-9]+)? + )? + ) + (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version +""" + + +class Version(_BaseVersion): + + _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) + + def __init__(self, version): + # Validate the version and parse it into pieces + match = self._regex.search(version) + if not match: + raise InvalidVersion("Invalid version: '{0}'".format(version)) + + # Store the parsed out pieces of the version + self._version = _Version( + epoch=int(match.group("epoch")) if match.group("epoch") else 0, + release=tuple(int(i) for i in match.group("release").split(".")), + pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), + post=_parse_letter_version( + match.group("post_l"), match.group("post_n1") or match.group("post_n2") + ), + dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), + local=_parse_local_version(match.group("local")), + ) + + # Generate a key which will be used for sorting + self._key = _cmpkey( + self._version.epoch, + self._version.release, + self._version.pre, + self._version.post, + self._version.dev, + self._version.local, + ) + + def __repr__(self): + return "<Version({0})>".format(repr(str(self))) + + def __str__(self): + parts = [] + + # Epoch + if self.epoch != 0: + parts.append("{0}!".format(self.epoch)) + + # Release segment + parts.append(".".join(str(x) for x in self.release)) + + # Pre-release + if self.pre is not None: + parts.append("".join(str(x) for x in self.pre)) + + # Post-release + if self.post is not None: + parts.append(".post{0}".format(self.post)) + + # Development release + if self.dev is not None: + parts.append(".dev{0}".format(self.dev)) + + # Local version segment + if self.local is not None: + parts.append("+{0}".format(self.local)) + + return "".join(parts) + + @property + def epoch(self): + return self._version.epoch + + @property + def release(self): + return self._version.release + + @property + def pre(self): + return self._version.pre + + @property + def post(self): + return self._version.post[1] if self._version.post else None + + @property + def dev(self): + return self._version.dev[1] if self._version.dev else None + + @property + def local(self): + if self._version.local: + return ".".join(str(x) for x in self._version.local) + else: + return None + + @property + def public(self): + return str(self).split("+", 1)[0] + + @property + def base_version(self): + parts = [] + + # Epoch + if self.epoch != 0: + parts.append("{0}!".format(self.epoch)) + + # Release segment + parts.append(".".join(str(x) for x in self.release)) + + return "".join(parts) + + @property + def is_prerelease(self): + return self.dev is not None or self.pre is not None + + @property + def is_postrelease(self): + return self.post is not None + + @property + def is_devrelease(self): + return self.dev is not None + + +def _parse_letter_version(letter, number): + if letter: + # We consider there to be an implicit 0 in a pre-release if there is + # not a numeral associated with it. + if number is None: + number = 0 + + # We normalize any letters to their lower case form + letter = letter.lower() + + # We consider some words to be alternate spellings of other words and + # in those cases we want to normalize the spellings to our preferred + # spelling. + if letter == "alpha": + letter = "a" + elif letter == "beta": + letter = "b" + elif letter in ["c", "pre", "preview"]: + letter = "rc" + elif letter in ["rev", "r"]: + letter = "post" + + return letter, int(number) + if not letter and number: + # We assume if we are given a number, but we are not given a letter + # then this is using the implicit post release syntax (e.g. 1.0-1) + letter = "post" + + return letter, int(number) + + +_local_version_separators = re.compile(r"[\._-]") + + +def _parse_local_version(local): + """ + Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). + """ + if local is not None: + return tuple( + part.lower() if not part.isdigit() else int(part) + for part in _local_version_separators.split(local) + ) + + +def _cmpkey(epoch, release, pre, post, dev, local): + # When we compare a release version, we want to compare it with all of the + # trailing zeros removed. So we'll use a reverse the list, drop all the now + # leading zeros until we come to something non zero, then take the rest + # re-reverse it back into the correct order and make it a tuple and use + # that for our sorting key. + release = tuple( + reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) + ) + + # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. + # We'll do this by abusing the pre segment, but we _only_ want to do this + # if there is not a pre or a post segment. If we have one of those then + # the normal sorting rules will handle this case correctly. + if pre is None and post is None and dev is not None: + pre = -Infinity + # Versions without a pre-release (except as noted above) should sort after + # those with one. + elif pre is None: + pre = Infinity + + # Versions without a post segment should sort before those with one. + if post is None: + post = -Infinity + + # Versions without a development segment should sort after those with one. + if dev is None: + dev = Infinity + + if local is None: + # Versions without a local segment should sort before those with one. + local = -Infinity + else: + # Versions with a local segment need that segment parsed to implement + # the sorting rules in PEP440. + # - Alpha numeric segments sort before numeric segments + # - Alpha numeric segments sort lexicographically + # - Numeric segments sort numerically + # - Shorter versions sort before longer versions when the prefixes + # match exactly + local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local) + + return epoch, release, pre, post, dev, local diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/__init__.py new file mode 100644 index 0000000..9c1a098 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/__init__.py @@ -0,0 +1,4 @@ +"""Wrappers to build Python packages using PEP 517 hooks +""" + +__version__ = '0.5.0' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/_in_process.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/_in_process.py new file mode 100644 index 0000000..d6524b6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/_in_process.py @@ -0,0 +1,207 @@ +"""This is invoked in a subprocess to call the build backend hooks. + +It expects: +- Command line args: hook_name, control_dir +- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec +- control_dir/input.json: + - {"kwargs": {...}} + +Results: +- control_dir/output.json + - {"return_val": ...} +""" +from glob import glob +from importlib import import_module +import os +from os.path import join as pjoin +import re +import shutil +import sys + +# This is run as a script, not a module, so it can't do a relative import +import compat + + +class BackendUnavailable(Exception): + """Raised if we cannot import the backend""" + + +def _build_backend(): + """Find and load the build backend""" + ep = os.environ['PEP517_BUILD_BACKEND'] + mod_path, _, obj_path = ep.partition(':') + try: + obj = import_module(mod_path) + except ImportError: + raise BackendUnavailable + if obj_path: + for path_part in obj_path.split('.'): + obj = getattr(obj, path_part) + return obj + + +def get_requires_for_build_wheel(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_wheel + except AttributeError: + return [] + else: + return hook(config_settings) + + +def prepare_metadata_for_build_wheel(metadata_directory, config_settings): + """Invoke optional prepare_metadata_for_build_wheel + + Implements a fallback by building a wheel if the hook isn't defined. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_wheel + except AttributeError: + return _get_wheel_metadata_from_wheel(backend, metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' + + +def _dist_info_files(whl_zip): + """Identify the .dist-info folder inside a wheel ZipFile.""" + res = [] + for path in whl_zip.namelist(): + m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) + if m: + res.append(path) + if res: + return res + raise Exception("No .dist-info folder found in wheel") + + +def _get_wheel_metadata_from_wheel( + backend, metadata_directory, config_settings): + """Build a wheel and extract the metadata from it. + + Fallback for when the build backend does not + define the 'get_wheel_metadata' hook. + """ + from zipfile import ZipFile + whl_basename = backend.build_wheel(metadata_directory, config_settings) + with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): + pass # Touch marker file + + whl_file = os.path.join(metadata_directory, whl_basename) + with ZipFile(whl_file) as zipf: + dist_info = _dist_info_files(zipf) + zipf.extractall(path=metadata_directory, members=dist_info) + return dist_info[0].split('/')[0] + + +def _find_already_built_wheel(metadata_directory): + """Check for a wheel already built during the get_wheel_metadata hook. + """ + if not metadata_directory: + return None + metadata_parent = os.path.dirname(metadata_directory) + if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): + return None + + whl_files = glob(os.path.join(metadata_parent, '*.whl')) + if not whl_files: + print('Found wheel built marker, but no .whl files') + return None + if len(whl_files) > 1: + print('Found multiple .whl files; unspecified behaviour. ' + 'Will call build_wheel.') + return None + + # Exactly one .whl file + return whl_files[0] + + +def build_wheel(wheel_directory, config_settings, metadata_directory=None): + """Invoke the mandatory build_wheel hook. + + If a wheel was already built in the + prepare_metadata_for_build_wheel fallback, this + will copy it rather than rebuilding the wheel. + """ + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return _build_backend().build_wheel(wheel_directory, config_settings, + metadata_directory) + + +def get_requires_for_build_sdist(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_sdist + except AttributeError: + return [] + else: + return hook(config_settings) + + +class _DummyException(Exception): + """Nothing should ever raise this exception""" + + +class GotUnsupportedOperation(Exception): + """For internal use when backend raises UnsupportedOperation""" + + +def build_sdist(sdist_directory, config_settings): + """Invoke the mandatory build_sdist hook.""" + backend = _build_backend() + try: + return backend.build_sdist(sdist_directory, config_settings) + except getattr(backend, 'UnsupportedOperation', _DummyException): + raise GotUnsupportedOperation + + +HOOK_NAMES = { + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'get_requires_for_build_sdist', + 'build_sdist', +} + + +def main(): + if len(sys.argv) < 3: + sys.exit("Needs args: hook_name, control_dir") + hook_name = sys.argv[1] + control_dir = sys.argv[2] + if hook_name not in HOOK_NAMES: + sys.exit("Unknown hook: %s" % hook_name) + hook = globals()[hook_name] + + hook_input = compat.read_json(pjoin(control_dir, 'input.json')) + + json_out = {'unsupported': False, 'return_val': None} + try: + json_out['return_val'] = hook(**hook_input['kwargs']) + except BackendUnavailable: + json_out['no_backend'] = True + except GotUnsupportedOperation: + json_out['unsupported'] = True + + compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/build.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/build.py new file mode 100644 index 0000000..ac6c949 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/build.py @@ -0,0 +1,108 @@ +"""Build a project using PEP 517 hooks. +""" +import argparse +import logging +import os +import contextlib +from pip._vendor import pytoml +import shutil +import errno +import tempfile + +from .envbuild import BuildEnvironment +from .wrappers import Pep517HookCaller + +log = logging.getLogger(__name__) + + +@contextlib.contextmanager +def tempdir(): + td = tempfile.mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + + +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 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 build(source_dir, dist, dest=None): + pyproject = os.path.join(source_dir, 'pyproject.toml') + dest = os.path.join(source_dir, dest or 'dist') + mkdir_p(dest) + + with open(pyproject) as f: + pyproject_data = pytoml.load(f) + # Ensure the mandatory data can be loaded + buildsys = pyproject_data['build-system'] + requires = buildsys['requires'] + backend = buildsys['build-backend'] + + hooks = Pep517HookCaller(source_dir, backend) + + with BuildEnvironment() as env: + env.pip_install(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): + # 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/check.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/check.py new file mode 100644 index 0000000..f4cdc6b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/check.py @@ -0,0 +1,202 @@ +"""Check a project and backend by attempting to build using PEP 517 hooks. +""" +import argparse +import logging +import os +from os.path import isfile, join as pjoin +from pip._vendor.pytoml import TomlError, load as toml_load +import shutil +from subprocess import CalledProcessError +import sys +import tarfile +from tempfile import mkdtemp +import zipfile + +from .colorlog import enable_colourful_output +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 open(pyproject) 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'] + log.info('Loaded pyproject.toml') + except (TomlError, KeyError): + log.error("Invalid pyproject.toml", exc_info=True) + return False + + hooks = Pep517HookCaller(source_dir, backend) + + 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): + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/colorlog.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/colorlog.py new file mode 100644 index 0000000..69c8a59 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/colorlog.py @@ -0,0 +1,115 @@ +"""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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/compat.py new file mode 100644 index 0000000..01c66fc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/compat.py @@ -0,0 +1,23 @@ +"""Handle reading and writing JSON in UTF-8, on Python 3 and 2.""" +import json +import sys + +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) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/envbuild.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/envbuild.py new file mode 100644 index 0000000..f7ac5f4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/envbuild.py @@ -0,0 +1,158 @@ +"""Build wheels/sdists by installing build deps to a temporary environment. +""" + +import os +import logging +from pip._vendor import pytoml +import shutil +from subprocess import check_call +import sys +from sysconfig import get_paths +from tempfile import mkdtemp + +from .wrappers import Pep517HookCaller + +log = logging.getLogger(__name__) + + +def _load_pyproject(source_dir): + with open(os.path.join(source_dir, 'pyproject.toml')) as f: + pyproject_data = pytoml.load(f) + buildsys = pyproject_data['build-system'] + return buildsys['requires'], buildsys['build-backend'] + + +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) + check_call([ + sys.executable, '-m', 'pip', 'install', '--ignore-installed', + '--prefix', self.path] + list(reqs)) + + 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 = _load_pyproject(source_dir) + hooks = Pep517HookCaller(source_dir, backend) + + 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 = _load_pyproject(source_dir) + hooks = Pep517HookCaller(source_dir, backend) + + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/wrappers.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/wrappers.py new file mode 100644 index 0000000..b14b899 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pep517/wrappers.py @@ -0,0 +1,163 @@ +from contextlib import contextmanager +import os +from os.path import dirname, abspath, join as pjoin +import shutil +from subprocess import check_call +import sys +from tempfile import mkdtemp + +from . import compat + +_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py') + + +@contextmanager +def tempdir(): + td = mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + + +class BackendUnavailable(Exception): + """Will be raised if the backend cannot be imported in the hook process.""" + + +class UnsupportedOperation(Exception): + """May be raised by build_sdist if the backend indicates that it can't.""" + + +def default_subprocess_runner(cmd, cwd=None, extra_environ=None): + """The default method of calling the wrapper subprocess.""" + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_call(cmd, cwd=cwd, env=env) + + +class Pep517HookCaller(object): + """A wrapper around a source directory to be built with a PEP 517 backend. + + source_dir : The path to the source directory, containing pyproject.toml. + backend : The build backend spec, as per PEP 517, from pyproject.toml. + """ + def __init__(self, source_dir, build_backend): + self.source_dir = abspath(source_dir) + self.build_backend = build_backend + self._subprocess_runner = default_subprocess_runner + + # TODO: Is this over-engineered? Maybe frontends only need to + # set this when creating the wrapper, not on every call. + @contextmanager + def subprocess_runner(self, runner): + prev = self._subprocess_runner + self._subprocess_runner = runner + yield + self._subprocess_runner = prev + + def get_requires_for_build_wheel(self, config_settings=None): + """Identify packages required for building a wheel + + Returns a list of dependency specifications, e.g.: + ["wheel >= 0.25", "setuptools"] + + This does not include requirements specified in pyproject.toml. + It returns the result of calling the equivalently named hook in a + subprocess. + """ + return self._call_hook('get_requires_for_build_wheel', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_wheel( + self, metadata_directory, config_settings=None): + """Prepare a *.dist-info folder with metadata for this project. + + Returns the name of the newly created folder. + + 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. + """ + return self._call_hook('prepare_metadata_for_build_wheel', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + }) + + def build_wheel( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build a wheel from this project. + + Returns the name of the newly created file. + + 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 metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_wheel', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + + 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.: + ["setuptools >= 26"] + + This does not include requirements specified in pyproject.toml. + It returns the result of calling the equivalently named hook in a + subprocess. + """ + return self._call_hook('get_requires_for_build_sdist', { + 'config_settings': config_settings + }) + + 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. + """ + return self._call_hook('build_sdist', { + 'sdist_directory': abspath(sdist_directory), + 'config_settings': config_settings, + }) + + 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). + if sys.version_info[0] == 2: + build_backend = self.build_backend.encode('ASCII') + else: + build_backend = self.build_backend + + with tempdir() as td: + compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'), + indent=2) + + # Run the hook in a subprocess + self._subprocess_runner( + [sys.executable, _in_proc_script, hook_name, td], + cwd=self.source_dir, + extra_environ={'PEP517_BUILD_BACKEND': build_backend} + ) + + data = compat.read_json(pjoin(td, 'output.json')) + if data.get('unsupported'): + raise UnsupportedOperation + if data.get('no_backend'): + raise BackendUnavailable + return data['return_val'] diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/__init__.py new file mode 100644 index 0000000..9c4fd8e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/__init__.py @@ -0,0 +1,3171 @@ +# coding: utf-8 +""" +Package resource API +-------------------- + +A resource is a logical file contained within a package, or a logical +subdirectory thereof. The package resource API expects resource names +to have their path parts separated with ``/``, *not* whatever the local +path separator is. Do not use os.path operations to manipulate resource +names being passed into the API. + +The package resource API is designed to work with normal filesystem packages, +.egg files, and unpacked .egg files. It can also work in a limited way with +.zip files and with custom PEP 302 loaders that support the ``get_data()`` +method. +""" + +from __future__ import absolute_import + +import sys +import os +import io +import time +import re +import types +import zipfile +import zipimport +import warnings +import stat +import functools +import pkgutil +import operator +import platform +import collections +import plistlib +import email.parser +import errno +import tempfile +import textwrap +import itertools +import inspect +from pkgutil import get_importer + +try: + import _imp +except ImportError: + # Python 3.2 compatibility + import imp as _imp + +try: + FileExistsError +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: + from os import mkdir, rename, unlink + WRITE_SUPPORT = True +except ImportError: + # no write support, probably under GAE + WRITE_SUPPORT = False + +from os import open as os_open +from os.path import isdir, split + +try: + import importlib.machinery as importlib_machinery + # access attribute to force import under delayed import mechanisms. + importlib_machinery.__name__ +except ImportError: + importlib_machinery = None + +from . import py31compat +from pip._vendor import appdirs +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') + + +__metaclass__ = type + + +if (3, 0) < sys.version_info < (3, 4): + raise RuntimeError("Python 3.4 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 +working_set = None +add_activation_listener = None +resources_stream = None +cleanup_resources = None +resource_dir = None +resource_stream = None +set_extraction_path = None +resource_isdir = None +resource_string = None +iter_entry_points = None +resource_listdir = None +resource_filename = None +resource_exists = None +_distribution_finders = None +_namespace_handlers = None +_namespace_packages = None + + +class PEP440Warning(RuntimeWarning): + """ + Used when there is an issue with a version or specifier not complying with + PEP 440. + """ + + +def parse_version(v): + try: + return packaging.version.Version(v) + except packaging.version.InvalidVersion: + return packaging.version.LegacyVersion(v) + + +_state_vars = {} + + +def _declare_state(vartype, **kw): + globals().update(kw) + _state_vars.update(dict.fromkeys(kw, vartype)) + + +def __getstate__(): + state = {} + g = globals() + for k, v in _state_vars.items(): + state[k] = g['_sget_' + v](g[k]) + return state + + +def __setstate__(state): + g = globals() + for k, v in state.items(): + g['_sset_' + _state_vars[k]](k, g[k], v) + return state + + +def _sget_dict(val): + return val.copy() + + +def _sset_dict(key, ob, state): + ob.clear() + ob.update(state) + + +def _sget_object(val): + return val.__getstate__() + + +def _sset_object(key, ob, state): + ob.__setstate__(state) + + +_sget_none = _sset_none = lambda *args: None + + +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 + 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 + current version of the OS. + + If this condition occurs for any other platform with a version in its + platform strings, this function should be extended accordingly. + """ + plat = get_build_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)) + except ValueError: + # not Mac OS X + pass + return plat + + +__all__ = [ + # Basic resource access and distribution/entry point discovery + 'require', 'run_script', 'get_provider', 'get_distribution', + 'load_entry_point', 'get_entry_map', 'get_entry_info', + 'iter_entry_points', + 'resource_string', 'resource_stream', 'resource_filename', + 'resource_listdir', 'resource_exists', 'resource_isdir', + + # Environmental control + 'declare_namespace', 'working_set', 'add_activation_listener', + 'find_distributions', 'set_extraction_path', 'cleanup_resources', + 'get_default_cache', + + # Primary implementation classes + 'Environment', 'WorkingSet', 'ResourceManager', + 'Distribution', 'Requirement', 'EntryPoint', + + # Exceptions + 'ResolutionError', 'VersionConflict', 'DistributionNotFound', + 'UnknownExtra', 'ExtractionError', + + # Warnings + 'PEP440Warning', + + # Parsing functions and string utilities + 'parse_requirements', 'parse_version', 'safe_name', 'safe_version', + 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', + 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker', + + # filesystem utilities + 'ensure_directory', 'normalize_path', + + # Distribution "precedence" constants + 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST', + + # "Provider" interfaces, implementations, and registration/lookup APIs + 'IMetadataProvider', 'IResourceProvider', 'FileMetadata', + 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider', + 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider', + 'register_finder', 'register_namespace_handler', 'register_loader_type', + 'fixup_namespace_packages', 'get_importer', + + # Warnings + 'PkgResourcesDeprecationWarning', + + # Deprecated/backward compatibility only + 'run_main', 'AvailableDistributions', +] + + +class ResolutionError(Exception): + """Abstract base for dependency resolution errors""" + + def __repr__(self): + return self.__class__.__name__ + repr(self.args) + + +class VersionConflict(ResolutionError): + """ + An already-installed version conflicts with the requested version. + + Should be initialized with the installed Distribution and the requested + Requirement. + """ + + _template = "{self.dist} is installed but {self.req} is required" + + @property + def dist(self): + return self.args[0] + + @property + def req(self): + return self.args[1] + + def report(self): + return self._template.format(**locals()) + + def with_context(self, required_by): + """ + If required_by is non-empty, return a version of self that is a + ContextualVersionConflict. + """ + if not required_by: + return self + args = self.args + (required_by,) + return ContextualVersionConflict(*args) + + +class ContextualVersionConflict(VersionConflict): + """ + A VersionConflict that accepts a third parameter, the set of the + requirements that required the installed Distribution. + """ + + _template = VersionConflict._template + ' by {self.required_by}' + + @property + def required_by(self): + return self.args[2] + + +class DistributionNotFound(ResolutionError): + """A requested distribution was not found""" + + _template = ("The '{self.req}' distribution was not found " + "and is required by {self.requirers_str}") + + @property + def req(self): + return self.args[0] + + @property + def requirers(self): + return self.args[1] + + @property + def requirers_str(self): + if not self.requirers: + return 'the application' + return ', '.join(self.requirers) + + def report(self): + return self._template.format(**locals()) + + def __str__(self): + return self.report() + + +class UnknownExtra(ResolutionError): + """Distribution doesn't have an "extra feature" of the given name""" + + +_provider_factories = {} + +PY_MAJOR = sys.version[:3] +EGG_DIST = 3 +BINARY_DIST = 2 +SOURCE_DIST = 1 +CHECKOUT_DIST = 0 +DEVELOP_DIST = -1 + + +def register_loader_type(loader_type, provider_factory): + """Register `provider_factory` to make providers for `loader_type` + + `loader_type` is the type or class of a PEP 302 ``module.__loader__``, + and `provider_factory` is a function that, passed a *module* object, + returns an ``IResourceProvider`` for that module. + """ + _provider_factories[loader_type] = provider_factory + + +def get_provider(moduleOrReq): + """Return an IResourceProvider for the named module or requirement""" + if isinstance(moduleOrReq, Requirement): + return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] + try: + module = sys.modules[moduleOrReq] + except KeyError: + __import__(moduleOrReq) + module = sys.modules[moduleOrReq] + loader = getattr(module, '__loader__', None) + return _find_adapter(_provider_factories, loader)(module) + + +def _macosx_vers(_cache=[]): + if not _cache: + version = platform.mac_ver()[0] + # fallback for MacPorts + if version == '': + plist = '/System/Library/CoreServices/SystemVersion.plist' + if os.path.exists(plist): + if hasattr(plistlib, 'readPlist'): + plist_content = plistlib.readPlist(plist) + if 'ProductVersion' in plist_content: + version = plist_content['ProductVersion'] + + _cache.append(version.split('.')) + return _cache[0] + + +def _macosx_arch(machine): + return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) + + +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. + """ + from sysconfig import get_platform + + plat = get_platform() + if sys.platform == "darwin" and not plat.startswith('macosx-'): + try: + version = _macosx_vers() + machine = os.uname()[4].replace(" ", "_") + return "macosx-%d.%d-%s" % ( + int(version[0]), int(version[1]), + _macosx_arch(machine), + ) + except ValueError: + # if someone is running a non-Mac darwin system, this will fall + # through to the default implementation + pass + return plat + + +macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)") +darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)") +# XXX backward compat +get_platform = get_build_platform + + +def compatible_platforms(provided, required): + """Can code for the `provided` platform run on the `required` platform? + + Returns true if either platform is ``None``, or the platforms are equal. + + XXX Needs compatibility checks for Linux and other unixy OSes. + """ + if provided is None or required is None or provided == required: + # easy case + return True + + # Mac OS X special cases + reqMac = macosVersionString.match(required) + if reqMac: + provMac = macosVersionString.match(provided) + + # is this a Mac package? + 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. + provDarwin = darwinVersionString.match(provided) + if provDarwin: + dversion = int(provDarwin.group(1)) + macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2)) + if dversion == 7 and macosversion >= "10.3" or \ + dversion == 8 and macosversion >= "10.4": + return True + # egg isn't macosx or legacy darwin + return False + + # are they the same major version and machine type? + if provMac.group(1) != reqMac.group(1) or \ + provMac.group(3) != reqMac.group(3): + return False + + # is the required OS major update >= the provided one? + if int(provMac.group(2)) > int(reqMac.group(2)): + return False + + return True + + # XXX Linux and other platforms' special cases should go here + return False + + +def run_script(dist_spec, script_name): + """Locate distribution `dist_spec` and run its `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + require(dist_spec)[0].run_script(script_name, ns) + + +# backward compatibility +run_main = run_script + + +def get_distribution(dist): + """Return a current distribution object for a Requirement or string""" + if isinstance(dist, six.string_types): + dist = Requirement.parse(dist) + if isinstance(dist, Requirement): + dist = get_provider(dist) + if not isinstance(dist, Distribution): + raise TypeError("Expected string, Requirement, or Distribution", dist) + return dist + + +def load_entry_point(dist, group, name): + """Return `name` entry point of `group` for `dist` or raise ImportError""" + return get_distribution(dist).load_entry_point(group, name) + + +def get_entry_map(dist, group=None): + """Return the entry point map for `group`, or the full entry map""" + return get_distribution(dist).get_entry_map(group) + + +def get_entry_info(dist, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return get_distribution(dist).get_entry_info(group, name) + + +class IMetadataProvider: + def has_metadata(name): + """Does the package's distribution contain the named metadata?""" + + def get_metadata(name): + """The named metadata resource as a string""" + + def get_metadata_lines(name): + """Yield named metadata resource as list of non-blank non-comment lines + + Leading and trailing whitespace is stripped from each line, and lines + with ``#`` as the first non-blank character are omitted.""" + + def metadata_isdir(name): + """Is the named metadata a directory? (like ``os.path.isdir()``)""" + + def metadata_listdir(name): + """List of metadata names in the directory (like ``os.listdir()``)""" + + def run_script(script_name, namespace): + """Execute the named script in the supplied namespace dictionary""" + + +class IResourceProvider(IMetadataProvider): + """An object that provides access to package resources""" + + def get_resource_filename(manager, resource_name): + """Return a true filesystem path for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_stream(manager, resource_name): + """Return a readable file-like object for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_string(manager, resource_name): + """Return a string containing the contents of `resource_name` + + `manager` must be an ``IResourceManager``""" + + def has_resource(resource_name): + """Does the package contain the named resource?""" + + def resource_isdir(resource_name): + """Is the named resource a directory? (like ``os.path.isdir()``)""" + + def resource_listdir(resource_name): + """List of resource names in the directory (like ``os.listdir()``)""" + + +class WorkingSet: + """A collection of active distributions on sys.path (or a similar list)""" + + def __init__(self, entries=None): + """Create working set from list of path entries (default=sys.path)""" + self.entries = [] + self.entry_keys = {} + self.by_key = {} + self.callbacks = [] + + if entries is None: + entries = sys.path + + for entry in entries: + self.add_entry(entry) + + @classmethod + def _build_master(cls): + """ + Prepare the master working set. + """ + ws = cls() + try: + from __main__ import __requires__ + except ImportError: + # The main program does not list any requirements + return ws + + # ensure the requirements are met + try: + ws.require(__requires__) + except VersionConflict: + return cls._build_from_requirements(__requires__) + + return ws + + @classmethod + def _build_from_requirements(cls, req_spec): + """ + Build a working set from a requirement spec. Rewrites sys.path. + """ + # try it without defaults already on sys.path + # by starting with an empty path + ws = cls([]) + reqs = parse_requirements(req_spec) + dists = ws.resolve(reqs, Environment()) + for dist in dists: + ws.add(dist) + + # add any missing entries from sys.path + for entry in sys.path: + if entry not in ws.entries: + ws.add_entry(entry) + + # then copy back to sys.path + sys.path[:] = ws.entries + return ws + + def add_entry(self, entry): + """Add a path item to ``.entries``, finding any distributions on it + + ``find_distributions(entry, True)`` is used to find distributions + corresponding to the path entry, and they are added. `entry` is + always appended to ``.entries``, even if it is already present. + (This is because ``sys.path`` can contain the same value more than + once, and the ``.entries`` of the ``sys.path`` WorkingSet should always + equal ``sys.path``.) + """ + self.entry_keys.setdefault(entry, []) + self.entries.append(entry) + for dist in find_distributions(entry, True): + self.add(dist, entry, False) + + def __contains__(self, dist): + """True if `dist` is the active distribution for its project""" + return self.by_key.get(dist.key) == dist + + def find(self, req): + """Find a distribution matching requirement `req` + + If there is an active distribution for the requested project, this + returns it as long as it meets the version requirement specified by + `req`. But, if there is an active distribution for the project and it + does *not* meet the `req` requirement, ``VersionConflict`` is raised. + If there is no active distribution for the requested project, ``None`` + is returned. + """ + dist = self.by_key.get(req.key) + if dist is not None and dist not in req: + # XXX add more info + raise VersionConflict(dist, req) + return dist + + def iter_entry_points(self, group, name=None): + """Yield entry point objects from `group` matching `name` + + If `name` is None, yields all entry points in `group` from all + distributions in the working set, otherwise only ones matching + both `group` and `name` are yielded (in distribution order). + """ + return ( + entry + for dist in self + for entry in dist.get_entry_map(group).values() + if name is None or name == entry.name + ) + + def run_script(self, requires, script_name): + """Locate distribution for `requires` and run `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + self.require(requires)[0].run_script(script_name, ns) + + def __iter__(self): + """Yield distributions for non-duplicate projects in the working set + + The yield order is the order in which the items' path entries were + added to the working set. + """ + seen = {} + for item in self.entries: + if item not in self.entry_keys: + # workaround a cache issue + continue + + for key in self.entry_keys[item]: + if key not in seen: + seen[key] = 1 + yield self.by_key[key] + + def add(self, dist, entry=None, insert=True, replace=False): + """Add `dist` to working set, associated with `entry` + + If `entry` is unspecified, it defaults to the ``.location`` of `dist`. + On exit from this routine, `entry` is added to the end of the working + set's ``.entries`` (if it wasn't already present). + + `dist` is only added to the working set if it's for a project that + doesn't already have a distribution in the set, unless `replace=True`. + If it's added, any callbacks registered with the ``subscribe()`` method + will be called. + """ + if insert: + dist.insert_on(self.entries, entry, replace=replace) + + if entry is None: + entry = dist.location + keys = self.entry_keys.setdefault(entry, []) + keys2 = self.entry_keys.setdefault(dist.location, []) + if not replace and dist.key in self.by_key: + # ignore hidden distros + return + + self.by_key[dist.key] = dist + 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, + replace_conflicting=False, extras=None): + """List all distributions needed to (recursively) meet `requirements` + + `requirements` must be a sequence of ``Requirement`` objects. `env`, + if supplied, should be an ``Environment`` instance. If + not supplied, it defaults to all distributions available within any + entry or distribution in the working set. `installer`, if supplied, + will be invoked with each requirement that cannot be met by an + already-installed distribution; it should return a ``Distribution`` or + ``None``. + + Unless `replace_conflicting=True`, raises a VersionConflict exception + if + any requirements are found on the path that have the correct name but + the wrong version. Otherwise, if an `installer` is supplied it will be + invoked to obtain the correct version of the requirement and activate + it. + + `extras` is a list of the extras to be used with these requirements. + This is important because extra requirements may look like `my_req; + extra = "my_extra"`, which would otherwise be interpreted as a purely + optional requirement. Instead, we want to be able to assert that these + requirements are truly required. + """ + + # set up the stack + requirements = list(requirements)[::-1] + # set of processed requirements + processed = {} + # key -> dist + best = {} + to_activate = [] + + req_extras = _ReqExtras() + + # Mapping of requirement to set of distributions that required it; + # useful for reporting info about conflicts. + required_by = collections.defaultdict(set) + + while requirements: + # process dependencies breadth-first + req = requirements.pop(0) + if req in processed: + # Ignore cyclic or redundant dependencies + continue + + if not req_extras.markers_pass(req, extras): + continue + + dist = best.get(req.key) + if dist is None: + # Find the best distribution and add it to the map + dist = self.by_key.get(req.key) + if dist is None or (dist not in req and replace_conflicting): + ws = self + if env is None: + if dist is None: + env = Environment(self.entries) + else: + # Use an empty environment and workingset to avoid + # any further conflicts with the conflicting + # distribution + env = Environment([]) + ws = WorkingSet([]) + dist = best[req.key] = env.best_match( + req, ws, installer, + replace_conflicting=replace_conflicting + ) + if dist is None: + requirers = required_by.get(req, None) + raise DistributionNotFound(req, requirers) + to_activate.append(dist) + if dist not in req: + # Oops, the "best" so far conflicts with a dependency + dependent_req = required_by[req] + raise VersionConflict(dist, req).with_context(dependent_req) + + # push the new requirements onto the stack + new_requirements = dist.requires(req.extras)[::-1] + requirements.extend(new_requirements) + + # Register the new requirements needed by req + for new_requirement in new_requirements: + required_by[new_requirement].add(req.project_name) + req_extras[new_requirement] = req.extras + + processed[req] = True + + # return list of distros to activate + return to_activate + + def find_plugins( + self, plugin_env, full_env=None, installer=None, fallback=True): + """Find all activatable distributions in `plugin_env` + + Example usage:: + + distributions, errors = working_set.find_plugins( + Environment(plugin_dirlist) + ) + # add plugins+libs to sys.path + map(working_set.add, distributions) + # display errors + print('Could not load', errors) + + The `plugin_env` should be an ``Environment`` instance that contains + only distributions that are in the project's "plugin directory" or + directories. The `full_env`, if supplied, should be an ``Environment`` + contains all currently-available distributions. If `full_env` is not + supplied, one is created automatically from the ``WorkingSet`` this + method is called on, which will typically mean that every directory on + ``sys.path`` will be scanned for distributions. + + `installer` is a standard installer callback as used by the + ``resolve()`` method. The `fallback` flag indicates whether we should + attempt to resolve older versions of a plugin if the newest version + cannot be resolved. + + This method returns a 2-tuple: (`distributions`, `error_info`), where + `distributions` is a list of the distributions found in `plugin_env` + that were loadable, along with any other distributions that are needed + to resolve their dependencies. `error_info` is a dictionary mapping + unloadable plugin distributions to an exception instance describing the + error that occurred. Usually this will be a ``DistributionNotFound`` or + ``VersionConflict`` instance. + """ + + plugin_projects = list(plugin_env) + # scan project names in alphabetic order + plugin_projects.sort() + + error_info = {} + distributions = {} + + if full_env is None: + env = Environment(self.entries) + env += plugin_env + else: + env = full_env + plugin_env + + shadow_set = self.__class__([]) + # put all our entries in shadow_set + list(map(shadow_set.add, self)) + + for project_name in plugin_projects: + + for dist in plugin_env[project_name]: + + req = [dist.as_requirement()] + + try: + resolvees = shadow_set.resolve(req, env, installer) + + except ResolutionError as v: + # save error info + error_info[dist] = v + if fallback: + # try the next older version of project + continue + else: + # give up on this project, keep going + break + + else: + list(map(shadow_set.add, resolvees)) + distributions.update(dict.fromkeys(resolvees)) + + # success, no need to try any more versions of this project + break + + distributions = list(distributions) + distributions.sort() + + return distributions, error_info + + def require(self, *requirements): + """Ensure that distributions matching `requirements` are activated + + `requirements` must be a string or a (possibly-nested) sequence + thereof, specifying the distributions and versions required. The + return value is a sequence of the distributions that needed to be + activated to fulfill the requirements; all relevant distributions are + included, even if they were already activated in this working set. + """ + needed = self.resolve(parse_requirements(requirements)) + + for dist in needed: + self.add(dist) + + return needed + + def subscribe(self, callback, existing=True): + """Invoke `callback` for all distributions + + If `existing=True` (default), + call on all existing ones, as well. + """ + if callback in self.callbacks: + return + self.callbacks.append(callback) + if not existing: + return + for dist in self: + callback(dist) + + def _added_new(self, dist): + for callback in self.callbacks: + callback(dist) + + def __getstate__(self): + return ( + self.entries[:], self.entry_keys.copy(), self.by_key.copy(), + self.callbacks[:] + ) + + def __setstate__(self, e_k_b_c): + entries, keys, by_key, callbacks = e_k_b_c + self.entries = entries[:] + self.entry_keys = keys.copy() + self.by_key = by_key.copy() + self.callbacks = callbacks[:] + + +class _ReqExtras(dict): + """ + Map each requirement to the extras that demanded it. + """ + + def markers_pass(self, req, extras=None): + """ + Evaluate markers for req against each extra that + demanded it. + + Return False if the req has a marker and fails + evaluation. Otherwise, return True. + """ + extra_evals = ( + req.marker.evaluate({'extra': extra}) + for extra in self.get(req, ()) + (extras or (None,)) + ) + return not req.marker or any(extra_evals) + + +class Environment: + """Searchable snapshot of distributions on a search path""" + + def __init__( + self, search_path=None, platform=get_supported_platform(), + python=PY_MAJOR): + """Snapshot distributions available on a search path + + Any distributions found on `search_path` are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. + + `platform` is an optional string specifying the name of the platform + that platform-specific distributions must be compatible with. If + unspecified, it defaults to the current platform. `python` is an + optional string naming the desired version of Python (e.g. ``'3.6'``); + it defaults to the current version. + + You may explicitly set `platform` (and/or `python`) to ``None`` if you + wish to map *all* distributions, not just those compatible with the + running platform or Python version. + """ + self._distmap = {} + self.platform = platform + self.python = python + self.scan(search_path) + + def can_add(self, dist): + """Is distribution `dist` acceptable for this environment? + + The distribution must match the platform and python version + requirements specified when this environment was created, or False + is returned. + """ + py_compat = ( + self.python is None + or dist.py_version is None + or dist.py_version == self.python + ) + return py_compat and compatible_platforms(dist.platform, self.platform) + + def remove(self, dist): + """Remove `dist` from the environment""" + self._distmap[dist.key].remove(dist) + + def scan(self, search_path=None): + """Scan `search_path` for distributions usable in this environment + + Any distributions found are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. Only distributions conforming to + the platform/python version defined at initialization are added. + """ + if search_path is None: + search_path = sys.path + + for item in search_path: + for dist in find_distributions(item): + self.add(dist) + + def __getitem__(self, project_name): + """Return a newest-to-oldest list of distributions for `project_name` + + Uses case-insensitive `project_name` comparison, assuming all the + project's distributions use their project's name converted to all + lowercase as their key. + + """ + distribution_key = project_name.lower() + return self._distmap.get(distribution_key, []) + + def add(self, dist): + """Add `dist` if we ``can_add()`` it and it has not already been added + """ + if self.can_add(dist) and dist.has_version(): + dists = self._distmap.setdefault(dist.key, []) + if dist not in dists: + dists.append(dist) + dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) + + def best_match( + self, req, working_set, installer=None, replace_conflicting=False): + """Find distribution best matching `req` and usable on `working_set` + + This calls the ``find(req)`` method of the `working_set` to see if a + suitable distribution is already active. (This may raise + ``VersionConflict`` if an unsuitable version of the project is already + active in the specified `working_set`.) If a suitable distribution + isn't active, this method returns the newest distribution in the + environment that meets the ``Requirement`` in `req`. If no suitable + distribution is found, and `installer` is supplied, then the result of + calling the environment's ``obtain(req, installer)`` method will be + returned. + """ + try: + dist = working_set.find(req) + except VersionConflict: + if not replace_conflicting: + raise + dist = None + if dist is not None: + return dist + for dist in self[req.key]: + if dist in req: + return dist + # try to download/install + return self.obtain(req, installer) + + def obtain(self, requirement, installer=None): + """Obtain a distribution matching `requirement` (e.g. via download) + + Obtain a distro that matches requirement (e.g. via download). In the + base ``Environment`` class, this routine just returns + ``installer(requirement)``, unless `installer` is None, in which case + None is returned instead. This method is a hook that allows subclasses + to attempt other ways of obtaining a distribution before falling back + to the `installer` argument.""" + if installer is not None: + return installer(requirement) + + def __iter__(self): + """Yield the unique project names of the available distributions""" + for key in self._distmap.keys(): + if self[key]: + yield key + + def __iadd__(self, other): + """In-place addition of a distribution or environment""" + if isinstance(other, Distribution): + self.add(other) + elif isinstance(other, Environment): + for project in other: + for dist in other[project]: + self.add(dist) + else: + raise TypeError("Can't add %r to environment" % (other,)) + return self + + def __add__(self, other): + """Add an environment or distribution to an environment""" + new = self.__class__([], platform=None, python=None) + for env in self, other: + new += env + return new + + +# XXX backward compatibility +AvailableDistributions = Environment + + +class ExtractionError(RuntimeError): + """An error occurred extracting a resource + + The following attributes are available from instances of this exception: + + manager + The resource manager that raised this exception + + cache_path + The base directory for resource extraction + + original_error + The exception instance that caused extraction to fail + """ + + +class ResourceManager: + """Manage resource extraction and packages""" + extraction_path = None + + def __init__(self): + self.cached_files = {} + + def resource_exists(self, package_or_requirement, resource_name): + """Does the named resource exist?""" + return get_provider(package_or_requirement).has_resource(resource_name) + + def resource_isdir(self, package_or_requirement, resource_name): + """Is the named resource an existing directory?""" + return get_provider(package_or_requirement).resource_isdir( + resource_name + ) + + def resource_filename(self, package_or_requirement, resource_name): + """Return a true filesystem path for specified resource""" + return get_provider(package_or_requirement).get_resource_filename( + self, resource_name + ) + + def resource_stream(self, package_or_requirement, resource_name): + """Return a readable file-like object for specified resource""" + return get_provider(package_or_requirement).get_resource_stream( + self, resource_name + ) + + def resource_string(self, package_or_requirement, resource_name): + """Return specified resource as a string""" + return get_provider(package_or_requirement).get_resource_string( + self, resource_name + ) + + def resource_listdir(self, package_or_requirement, resource_name): + """List the contents of the named resource directory""" + return get_provider(package_or_requirement).resource_listdir( + resource_name + ) + + def extraction_error(self): + """Give an error message for problems extracting file(s)""" + + old_exc = sys.exc_info()[1] + cache_path = self.extraction_path or get_default_cache() + + tmpl = textwrap.dedent(""" + Can't extract file(s) to egg cache + + The following error occurred while trying to extract file(s) + to the Python egg cache: + + {old_exc} + + The Python egg cache directory is currently set to: + + {cache_path} + + Perhaps your account does not have write access to this directory? + You can change the cache directory by setting the PYTHON_EGG_CACHE + environment variable to point to an accessible directory. + """).lstrip() + err = ExtractionError(tmpl.format(**locals())) + err.manager = self + err.cache_path = cache_path + err.original_error = old_exc + raise err + + def get_cache_path(self, archive_name, names=()): + """Return absolute location in cache for `archive_name` and `names` + + The parent directory of the resulting path will be created if it does + not already exist. `archive_name` should be the base filename of the + enclosing egg (which may not be the name of the enclosing zipfile!), + including its ".egg" extension. `names`, if provided, should be a + sequence of path name parts "under" the egg's extraction location. + + This method should only be called by resource providers that need to + obtain an extraction location, and only for names they intend to + extract, as it tracks the generated names for possible cleanup later. + """ + extract_path = self.extraction_path or get_default_cache() + target_path = os.path.join(extract_path, archive_name + '-tmp', *names) + try: + _bypass_ensure_directory(target_path) + except Exception: + self.extraction_error() + + self._warn_unsafe_extraction_path(extract_path) + + self.cached_files[target_path] = 1 + return target_path + + @staticmethod + def _warn_unsafe_extraction_path(path): + """ + If the default extraction path is overridden and set to an insecure + location, such as /tmp, it opens up an opportunity for an attacker to + replace an extracted file with an unauthorized payload. Warn the user + if a known insecure location is used. + + See Distribute #375 for more details. + """ + if os.name == 'nt' and not path.startswith(os.environ['windir']): + # On Windows, permissions are generally restrictive by default + # and temp directories are not writable by other users, so + # bypass the warning. + return + 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 " + "location (set with .set_extraction_path or the " + "PYTHON_EGG_CACHE environment variable)." % path + ) + warnings.warn(msg, UserWarning) + + def postprocess(self, tempname, filename): + """Perform any platform-specific postprocessing of `tempname` + + This is where Mac header rewrites should be done; other platforms don't + have anything special they should do. + + Resource providers should call this method ONLY after successfully + extracting a compressed resource. They must NOT call it on resources + that are already in the filesystem. + + `tempname` is the current (temporary) name of the file, and `filename` + is the name it will be renamed to by the caller after this routine + returns. + """ + + if os.name == 'posix': + # Make the resource executable + mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777 + os.chmod(tempname, mode) + + def set_extraction_path(self, path): + """Set the base path where resources will be extracted to, if needed. + + If you do not call this routine before any extractions take place, the + path defaults to the return value of ``get_default_cache()``. (Which + is based on the ``PYTHON_EGG_CACHE`` environment variable, with various + platform-specific fallbacks. See that routine's documentation for more + details.) + + Resources are extracted to subdirectories of this path based upon + information given by the ``IResourceProvider``. You may set this to a + temporary directory, but then you must call ``cleanup_resources()`` to + delete the extracted files when done. There is no guarantee that + ``cleanup_resources()`` will be able to remove all extracted files. + + (Note: you may not change the extraction path for a given resource + manager once resources have been extracted, unless you first call + ``cleanup_resources()``.) + """ + if self.cached_files: + raise ValueError( + "Can't change extraction path, files already extracted" + ) + + self.extraction_path = path + + def cleanup_resources(self, force=False): + """ + Delete all extracted resource files and directories, returning a list + of the file and directory names that could not be successfully removed. + This function does not have any concurrency protection, so it should + generally only be called when the extraction path is a temporary + directory exclusive to a single process. This method is not + automatically called; you must call it explicitly or register it as an + ``atexit`` function if you wish to ensure cleanup of a temporary + directory used for extractions. + """ + # XXX + + +def get_default_cache(): + """ + Return the ``PYTHON_EGG_CACHE`` environment variable + or a platform-relevant user cache dir for an app + named "Python-Eggs". + """ + return ( + os.environ.get('PYTHON_EGG_CACHE') + or appdirs.user_cache_dir(appname='Python-Eggs') + ) + + +def safe_name(name): + """Convert an arbitrary string to a standard distribution name + + Any runs of non-alphanumeric/. characters are replaced with a single '-'. + """ + return re.sub('[^A-Za-z0-9.]+', '-', name) + + +def safe_version(version): + """ + Convert an arbitrary string to a standard version string + """ + try: + # normalize the version + return str(packaging.version.Version(version)) + except packaging.version.InvalidVersion: + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def safe_extra(extra): + """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. + """ + return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() + + +def to_filename(name): + """Convert a project or version name to its filename-escaped form + + Any '-' characters are currently replaced with '_'. + """ + return name.replace('-', '_') + + +def invalid_marker(text): + """ + Validate text as a PEP 508 environment marker; return an exception + if invalid or False otherwise. + """ + try: + evaluate_marker(text) + except SyntaxError as e: + e.filename = None + e.lineno = None + return e + return False + + +def evaluate_marker(text, extra=None): + """ + Evaluate a PEP 508 environment marker. + Return a boolean indicating the marker result in this environment. + Raise SyntaxError if marker is invalid. + + This implementation uses the 'pyparsing' module. + """ + try: + marker = packaging.markers.Marker(text) + return marker.evaluate() + except packaging.markers.InvalidMarker as e: + raise SyntaxError(e) + + +class NullProvider: + """Try to implement resources and metadata for arbitrary PEP 302 loaders""" + + egg_name = None + egg_info = None + loader = None + + def __init__(self, module): + self.loader = getattr(module, '__loader__', None) + self.module_path = os.path.dirname(getattr(module, '__file__', '')) + + def get_resource_filename(self, manager, resource_name): + return self._fn(self.module_path, resource_name) + + def get_resource_stream(self, manager, resource_name): + return io.BytesIO(self.get_resource_string(manager, resource_name)) + + def get_resource_string(self, manager, resource_name): + return self._get(self._fn(self.module_path, resource_name)) + + def has_resource(self, resource_name): + return self._has(self._fn(self.module_path, resource_name)) + + def has_metadata(self, name): + return self.egg_info and self._has(self._fn(self.egg_info, name)) + + def get_metadata(self, name): + if not self.egg_info: + return "" + value = self._get(self._fn(self.egg_info, name)) + return value.decode('utf-8') if six.PY3 else value + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + def resource_isdir(self, resource_name): + return self._isdir(self._fn(self.module_path, resource_name)) + + def metadata_isdir(self, name): + return self.egg_info and self._isdir(self._fn(self.egg_info, name)) + + def resource_listdir(self, resource_name): + return self._listdir(self._fn(self.module_path, resource_name)) + + def metadata_listdir(self, name): + if self.egg_info: + return self._listdir(self._fn(self.egg_info, name)) + return [] + + def run_script(self, script_name, namespace): + script = 'scripts/' + script_name + if not self.has_metadata(script): + raise ResolutionError( + "Script {script!r} not found in metadata at {self.egg_info!r}" + .format(**locals()), + ) + script_text = self.get_metadata(script).replace('\r\n', '\n') + script_text = script_text.replace('\r', '\n') + script_filename = self._fn(self.egg_info, script) + namespace['__file__'] = script_filename + if os.path.exists(script_filename): + source = open(script_filename).read() + code = compile(source, script_filename, 'exec') + exec(code, namespace, namespace) + else: + from linecache import cache + cache[script_filename] = ( + len(script_text), 0, script_text.split('\n'), script_filename + ) + script_code = compile(script_text, script_filename, 'exec') + exec(script_code, namespace, namespace) + + def _has(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _isdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _listdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _fn(self, base, resource_name): + if resource_name: + return os.path.join(base, *resource_name.split('/')) + return base + + def _get(self, path): + if hasattr(self.loader, 'get_data'): + return self.loader.get_data(path) + raise NotImplementedError( + "Can't perform this operation for loaders without 'get_data()'" + ) + + +register_loader_type(object, NullProvider) + + +class EggProvider(NullProvider): + """Provider based on a virtual filesystem""" + + def __init__(self, module): + NullProvider.__init__(self, 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) + + +class DefaultProvider(EggProvider): + """Provides access to package resources in the filesystem""" + + def _has(self, path): + return os.path.exists(path) + + def _isdir(self, path): + return os.path.isdir(path) + + def _listdir(self, path): + return os.listdir(path) + + def get_resource_stream(self, manager, resource_name): + return open(self._fn(self.module_path, resource_name), 'rb') + + def _get(self, path): + with open(path, 'rb') as stream: + return stream.read() + + @classmethod + def _register(cls): + loader_names = 'SourceFileLoader', 'SourcelessFileLoader', + for name in loader_names: + loader_cls = getattr(importlib_machinery, name, type(None)) + register_loader_type(loader_cls, cls) + + +DefaultProvider._register() + + +class EmptyProvider(NullProvider): + """Provider that returns nothing for all requests""" + + module_path = None + + _isdir = _has = lambda self, path: False + + def _get(self, path): + return '' + + def _listdir(self, path): + return [] + + def __init__(self): + pass + + +empty_provider = EmptyProvider() + + +class ZipManifests(dict): + """ + zip manifest builder + """ + + @classmethod + def build(cls, path): + """ + Build a dictionary similar to the zipimport directory + caches, except instead of tuples, store ZipInfo objects. + + Use a platform-specific path separator (os.sep) for the path keys + for compatibility with pypy on Windows. + """ + with zipfile.ZipFile(path) as zfile: + items = ( + ( + name.replace('/', os.sep), + zfile.getinfo(name), + ) + for name in zfile.namelist() + ) + return dict(items) + + load = build + + +class MemoizedZipManifests(ZipManifests): + """ + Memoized zipfile manifests. + """ + manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') + + def load(self, path): + """ + Load a manifest at path or return a suitable manifest already loaded. + """ + path = os.path.normpath(path) + mtime = os.stat(path).st_mtime + + if path not in self or self[path].mtime != mtime: + manifest = self.build(path) + self[path] = self.manifest_mod(manifest, mtime) + + return self[path].manifest + + +class ZipProvider(EggProvider): + """Resource support for zips and eggs""" + + eagers = None + _zip_manifests = MemoizedZipManifests() + + def __init__(self, module): + EggProvider.__init__(self, module) + self.zip_pre = self.loader.archive + os.sep + + def _zipinfo_name(self, fspath): + # Convert a virtual filename (full path to file) into a zipfile subpath + # usable with the zipimport directory cache for our target archive + fspath = fspath.rstrip(os.sep) + if fspath == self.loader.archive: + return '' + if fspath.startswith(self.zip_pre): + return fspath[len(self.zip_pre):] + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.zip_pre) + ) + + def _parts(self, zip_path): + # Convert a zipfile subpath into an egg-relative path part list. + # pseudo-fs path + fspath = self.zip_pre + zip_path + if fspath.startswith(self.egg_root + os.sep): + return fspath[len(self.egg_root) + 1:].split(os.sep) + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.egg_root) + ) + + @property + def zipinfo(self): + return self._zip_manifests.load(self.loader.archive) + + def get_resource_filename(self, manager, resource_name): + if not self.egg_name: + raise NotImplementedError( + "resource_filename() only supported for .egg, not .zip" + ) + # no need to lock for extraction, since we use temp names + zip_path = self._resource_to_zip(resource_name) + eagers = self._get_eager_resources() + if '/'.join(self._parts(zip_path)) in eagers: + for name in eagers: + self._extract_resource(manager, self._eager_to_zip(name)) + return self._extract_resource(manager, zip_path) + + @staticmethod + def _get_date_and_size(zip_stat): + size = zip_stat.file_size + # ymdhms+wday, yday, dst + date_time = zip_stat.date_time + (0, 0, -1) + # 1980 offset already done + timestamp = time.mktime(date_time) + return timestamp, size + + def _extract_resource(self, manager, zip_path): + + if zip_path in self._index(): + for name in self._index()[zip_path]: + last = self._extract_resource( + manager, os.path.join(zip_path, name) + ) + # return the extracted directory name + return os.path.dirname(last) + + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + + if not WRITE_SUPPORT: + raise IOError('"os.rename" and "os.unlink" are not supported ' + 'on this platform') + try: + + real_path = manager.get_cache_path( + self.egg_name, self._parts(zip_path) + ) + + if self._is_current(real_path, zip_path): + return real_path + + outf, tmpnam = _mkstemp( + ".$extract", + dir=os.path.dirname(real_path), + ) + os.write(outf, self.loader.get_data(zip_path)) + os.close(outf) + utime(tmpnam, (timestamp, timestamp)) + manager.postprocess(tmpnam, real_path) + + try: + rename(tmpnam, real_path) + + except os.error: + if os.path.isfile(real_path): + if self._is_current(real_path, zip_path): + # the file became current since it was checked above, + # so proceed. + return real_path + # Windows, del old file and retry + elif os.name == 'nt': + unlink(real_path) + rename(tmpnam, real_path) + return real_path + raise + + except os.error: + # report a user-friendly error + manager.extraction_error() + + return real_path + + def _is_current(self, file_path, zip_path): + """ + Return True if the file_path is current for this zip_path + """ + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + if not os.path.isfile(file_path): + return False + stat = os.stat(file_path) + if stat.st_size != size or stat.st_mtime != timestamp: + return False + # check that the contents match + zip_contents = self.loader.get_data(zip_path) + with open(file_path, 'rb') as f: + file_contents = f.read() + return zip_contents == file_contents + + def _get_eager_resources(self): + if self.eagers is None: + eagers = [] + for name in ('native_libs.txt', 'eager_resources.txt'): + if self.has_metadata(name): + eagers.extend(self.get_metadata_lines(name)) + self.eagers = eagers + return self.eagers + + def _index(self): + try: + return self._dirindex + except AttributeError: + ind = {} + for path in self.zipinfo: + parts = path.split(os.sep) + while parts: + parent = os.sep.join(parts[:-1]) + if parent in ind: + ind[parent].append(parts[-1]) + break + else: + ind[parent] = [parts.pop()] + self._dirindex = ind + return ind + + def _has(self, fspath): + zip_path = self._zipinfo_name(fspath) + return zip_path in self.zipinfo or zip_path in self._index() + + def _isdir(self, fspath): + return self._zipinfo_name(fspath) in self._index() + + def _listdir(self, fspath): + return list(self._index().get(self._zipinfo_name(fspath), ())) + + def _eager_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.egg_root, resource_name)) + + def _resource_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.module_path, resource_name)) + + +register_loader_type(zipimport.zipimporter, ZipProvider) + + +class FileMetadata(EmptyProvider): + """Metadata handler for standalone PKG-INFO files + + Usage:: + + metadata = FileMetadata("/path/to/PKG-INFO") + + This provider rejects all data and metadata requests except for PKG-INFO, + which is treated as existing, and will be the contents of the file at + the provided location. + """ + + def __init__(self, path): + self.path = path + + def has_metadata(self, name): + return name == 'PKG-INFO' and os.path.isfile(self.path) + + def get_metadata(self, name): + if name != 'PKG-INFO': + raise KeyError("No metadata except PKG-INFO is available") + + with io.open(self.path, encoding='utf-8', errors="replace") as f: + metadata = f.read() + self._warn_on_replacement(metadata) + return metadata + + def _warn_on_replacement(self, metadata): + # Python 2.7 compat for: replacement_char = '�' + replacement_char = b'\xef\xbf\xbd'.decode('utf-8') + if replacement_char in metadata: + tmpl = "{self.path} could not be properly decoded in UTF-8" + msg = tmpl.format(**locals()) + warnings.warn(msg) + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + +class PathMetadata(DefaultProvider): + """Metadata provider for egg directories + + Usage:: + + # Development eggs: + + egg_info = "/path/to/PackageName.egg-info" + base_dir = os.path.dirname(egg_info) + metadata = PathMetadata(base_dir, egg_info) + dist_name = os.path.splitext(os.path.basename(egg_info))[0] + dist = Distribution(basedir, project_name=dist_name, metadata=metadata) + + # Unpacked egg directories: + + egg_path = "/path/to/PackageName-ver-pyver-etc.egg" + metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO')) + dist = Distribution.from_filename(egg_path, metadata=metadata) + """ + + def __init__(self, path, egg_info): + self.module_path = path + self.egg_info = egg_info + + +class EggMetadata(ZipProvider): + """Metadata provider for .egg files""" + + def __init__(self, importer): + """Create a metadata provider from a zipimporter""" + + self.zip_pre = importer.archive + os.sep + self.loader = importer + if importer.prefix: + self.module_path = os.path.join(importer.archive, importer.prefix) + else: + self.module_path = importer.archive + self._setup_prefix() + + +_declare_state('dict', _distribution_finders={}) + + +def register_finder(importer_type, distribution_finder): + """Register `distribution_finder` to find distributions in sys.path items + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `distribution_finder` is a callable that, passed a path + item and the importer instance, yields ``Distribution`` instances found on + that path item. See ``pkg_resources.find_on_path`` for an example.""" + _distribution_finders[importer_type] = distribution_finder + + +def find_distributions(path_item, only=False): + """Yield distributions accessible via `path_item`""" + importer = get_importer(path_item) + finder = _find_adapter(_distribution_finders, importer) + return finder(importer, path_item, only) + + +def find_eggs_in_zip(importer, path_item, only=False): + """ + Find eggs in zip files; possibly multiple nested eggs. + """ + if importer.archive.endswith('.whl'): + # wheels are not supported with this finder + # they don't have PKG-INFO metadata, and won't ever contain eggs + return + metadata = EggMetadata(importer) + if metadata.has_metadata('PKG-INFO'): + yield Distribution.from_filename(path_item, metadata=metadata) + if only: + # don't yield nested distros + return + for subitem in metadata.resource_listdir('/'): + if _is_egg_path(subitem): + subpath = os.path.join(path_item, subitem) + dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) + for dist in dists: + yield dist + elif subitem.lower().endswith('.dist-info'): + subpath = os.path.join(path_item, subitem) + submeta = EggMetadata(zipimport.zipimporter(subpath)) + submeta.egg_info = subpath + yield Distribution.from_location(path_item, subitem, submeta) + + +register_finder(zipimport.zipimporter, find_eggs_in_zip) + + +def find_nothing(importer, path_item, only=False): + return () + + +register_finder(object, find_nothing) + + +def _by_version_descending(names): + """ + Given a list of filenames, return them in descending order + by version number. + + >>> 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'] + >>> 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'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg'] + """ + 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 sorted(names, key=_by_version, reverse=True) + + +def find_on_path(importer, path_item, only=False): + """Yield distributions accessible on a sys.path directory""" + path_item = _normalize_cached(path_item) + + if _is_unpacked_egg(path_item): + yield Distribution.from_filename( + path_item, metadata=PathMetadata( + path_item, os.path.join(path_item, 'EGG-INFO') + ) + ) + return + + entries = safe_listdir(path_item) + + # for performance, before sorting by version, + # screen entries for only those that will yield + # distributions + filtered = ( + entry + for entry in entries + if dist_factory(path_item, entry, only) + ) + + # scan for .egg and .egg-info in directory + path_item_entries = _by_version_descending(filtered) + for entry in path_item_entries: + fullpath = os.path.join(path_item, entry) + factory = dist_factory(path_item, entry, only) + for dist in factory(fullpath): + yield dist + + +def dist_factory(path_item, entry, only): + """ + Return a dist_factory for a path_item and entry + """ + lower = entry.lower() + is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info'))) + return ( + distributions_from_metadata + if is_meta else + find_distributions + if not only and _is_egg_path(entry) else + resolve_egg_link + if not only and lower.endswith('.egg-link') else + NoDists() + ) + + +class NoDists: + """ + >>> bool(NoDists()) + False + + >>> list(NoDists()('anything')) + [] + """ + def __bool__(self): + return False + if six.PY2: + __nonzero__ = __bool__ + + def __call__(self, fullpath): + return iter(()) + + +def safe_listdir(path): + """ + Attempt to list contents of path, but suppress some exceptions. + """ + try: + return os.listdir(path) + except (PermissionError, NotADirectoryError): + pass + 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: + raise + return () + + +def distributions_from_metadata(path): + root = os.path.dirname(path) + if os.path.isdir(path): + if len(os.listdir(path)) == 0: + # empty metadata dir; skip + return + metadata = PathMetadata(root, path) + else: + metadata = FileMetadata(path) + entry = os.path.basename(path) + yield Distribution.from_location( + root, entry, metadata, precedence=DEVELOP_DIST, + ) + + +def non_empty_lines(path): + """ + Yield non-empty lines from file at path + """ + with open(path) as f: + for line in f: + line = line.strip() + if line: + yield line + + +def resolve_egg_link(path): + """ + Given a path to an .egg-link, resolve distributions + present in the referenced path. + """ + referenced_paths = non_empty_lines(path) + resolved_paths = ( + os.path.join(os.path.dirname(path), ref) + for ref in referenced_paths + ) + dist_groups = map(find_distributions, resolved_paths) + return next(dist_groups, ()) + + +register_finder(pkgutil.ImpImporter, find_on_path) + +if hasattr(importlib_machinery, 'FileFinder'): + register_finder(importlib_machinery.FileFinder, find_on_path) + +_declare_state('dict', _namespace_handlers={}) +_declare_state('dict', _namespace_packages={}) + + +def register_namespace_handler(importer_type, namespace_handler): + """Register `namespace_handler` to declare namespace packages + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `namespace_handler` is a callable like this:: + + def namespace_handler(importer, path_entry, moduleName, module): + # return a path_entry to use for child packages + + Namespace handlers are only called if the importer object has already + agreed that it can handle the relevant path item, and they should only + return a subpath if the module __path__ does not already contain an + equivalent subpath. For an example namespace handler, see + ``pkg_resources.file_ns_handler``. + """ + _namespace_handlers[importer_type] = namespace_handler + + +def _handle_ns(packageName, path_item): + """Ensure that named package includes a subpath of path_item (if needed)""" + + importer = get_importer(path_item) + if importer is None: + return None + + # capture warnings due to #1111 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + loader = importer.find_module(packageName) + + if loader is None: + return None + module = sys.modules.get(packageName) + if module is None: + module = sys.modules[packageName] = types.ModuleType(packageName) + module.__path__ = [] + _set_parent_ns(packageName) + elif not hasattr(module, '__path__'): + raise TypeError("Not a package:", packageName) + handler = _find_adapter(_namespace_handlers, importer) + subpath = handler(importer, path_item, packageName, module) + if subpath is not None: + path = module.__path__ + path.append(subpath) + loader.load_module(packageName) + _rebuild_mod_path(path, packageName, module) + return subpath + + +def _rebuild_mod_path(orig_path, package_name, module): + """ + Rebuild module.__path__ ensuring that all entries are ordered + corresponding to their sys.path order + """ + sys_path = [_normalize_cached(p) for p in sys.path] + + def safe_sys_path_index(entry): + """ + Workaround for #520 and #513. + """ + try: + return sys_path.index(entry) + except ValueError: + return float('inf') + + def position_in_sys_path(path): + """ + Return the ordinal of the path based on its position in sys.path + """ + path_parts = path.split(os.sep) + module_parts = package_name.count('.') + 1 + parts = path_parts[:-module_parts] + return safe_sys_path_index(_normalize_cached(os.sep.join(parts))) + + new_path = sorted(orig_path, key=position_in_sys_path) + new_path = [_normalize_cached(p) for p in new_path] + + if isinstance(module.__path__, list): + module.__path__[:] = new_path + else: + module.__path__ = new_path + + +def declare_namespace(packageName): + """Declare that package 'packageName' is a namespace package""" + + _imp.acquire_lock() + try: + if packageName in _namespace_packages: + return + + path = sys.path + parent, _, _ = packageName.rpartition('.') + + if parent: + declare_namespace(parent) + if parent not in _namespace_packages: + __import__(parent) + try: + path = sys.modules[parent].__path__ + except AttributeError: + raise TypeError("Not a package:", parent) + + # Track what packages are namespaces, so when new path items are added, + # they can be updated + _namespace_packages.setdefault(parent or None, []).append(packageName) + _namespace_packages.setdefault(packageName, []) + + for path_item in path: + # Ensure all the parent's path items are reflected in the child, + # if they apply + _handle_ns(packageName, path_item) + + finally: + _imp.release_lock() + + +def fixup_namespace_packages(path_item, parent=None): + """Ensure that previously-declared namespace packages include path_item""" + _imp.acquire_lock() + try: + for package in _namespace_packages.get(parent, ()): + subpath = _handle_ns(package, path_item) + if subpath: + fixup_namespace_packages(subpath, package) + finally: + _imp.release_lock() + + +def file_ns_handler(importer, path_item, packageName, module): + """Compute an ns-package subpath for a filesystem or zipfile importer""" + + subpath = os.path.join(path_item, packageName.split('.')[-1]) + normalized = _normalize_cached(subpath) + for item in module.__path__: + if _normalize_cached(item) == normalized: + break + else: + # Only return the path if it's not already there + return subpath + + +register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) +register_namespace_handler(zipimport.zipimporter, file_ns_handler) + +if hasattr(importlib_machinery, 'FileFinder'): + register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) + + +def null_ns_handler(importer, path_item, packageName, module): + return None + + +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)))) + + +def _cygwin_patch(filename): # pragma: nocover + """ + Contrary to POSIX 2008, on Cygwin, getcwd (3) contains + symlink components. Using + os.path.abspath() works around this limitation. A fix in os.getcwd() + would probably better, in Cygwin even more so, except + that this seems to be by design... + """ + return os.path.abspath(filename) if sys.platform == 'cygwin' else filename + + +def _normalize_cached(filename, _cache={}): + try: + return _cache[filename] + except KeyError: + _cache[filename] = result = normalize_path(filename) + return result + + +def _is_egg_path(path): + """ + Determine if given path appears to be an egg. + """ + return path.lower().endswith('.egg') + + +def _is_unpacked_egg(path): + """ + Determine if given path appears to be an unpacked egg. + """ + return ( + _is_egg_path(path) and + os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO')) + ) + + +def _set_parent_ns(packageName): + parts = packageName.split('.') + name = parts.pop() + if parts: + parent = '.'.join(parts) + 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""" + (?P<name>[^-]+) ( + -(?P<ver>[^-]+) ( + -py(?P<pyver>[^-]+) ( + -(?P<plat>.+) + )? + )? + )? + """, + re.VERBOSE | re.IGNORECASE, +).match + + +class EntryPoint: + """Object representing an advertised importable object""" + + def __init__(self, name, module_name, attrs=(), extras=(), dist=None): + if not MODULE(module_name): + raise ValueError("Invalid module name", module_name) + self.name = name + self.module_name = module_name + self.attrs = tuple(attrs) + self.extras = tuple(extras) + self.dist = dist + + def __str__(self): + s = "%s = %s" % (self.name, self.module_name) + if self.attrs: + s += ':' + '.'.join(self.attrs) + if self.extras: + s += ' [%s]' % ','.join(self.extras) + return s + + def __repr__(self): + return "EntryPoint.parse(%r)" % str(self) + + def load(self, require=True, *args, **kwargs): + """ + Require packages for this EntryPoint, then resolve it. + """ + if not require or args or kwargs: + warnings.warn( + "Parameters to load are deprecated. Call .resolve and " + ".require separately.", + PkgResourcesDeprecationWarning, + stacklevel=2, + ) + if require: + self.require(*args, **kwargs) + return self.resolve() + + def resolve(self): + """ + Resolve the entry point from its module and attrs. + """ + module = __import__(self.module_name, fromlist=['__name__'], level=0) + try: + return functools.reduce(getattr, self.attrs, module) + except AttributeError as exc: + raise ImportError(str(exc)) + + def require(self, env=None, installer=None): + if self.extras and not self.dist: + raise UnknownExtra("Can't require() without a distribution", self) + + # Get the requirements for this entry point with all its extras and + # then resolve them. We have to pass `extras` along when resolving so + # that the working set knows what extras we want. Otherwise, for + # dist-info distributions, the working set will assume that the + # requirements for that extra are purely optional and skip over them. + reqs = self.dist.requires(self.extras) + items = working_set.resolve(reqs, env, installer, extras=self.extras) + list(map(working_set.add, items)) + + pattern = re.compile( + r'\s*' + r'(?P<name>.+?)\s*' + r'=\s*' + r'(?P<module>[\w.]+)\s*' + r'(:\s*(?P<attr>[\w.]+))?\s*' + r'(?P<extras>\[.*\])?\s*$' + ) + + @classmethod + def parse(cls, src, dist=None): + """Parse a single entry point from string `src` + + Entry point syntax follows the form:: + + name = some.module:some.attr [extra1, extra2] + + The entry name and module name are required, but the ``:attrs`` and + ``[extras]`` parts are optional + """ + m = cls.pattern.match(src) + if not m: + msg = "EntryPoint must be in 'name=module:attrs [extras]' format" + raise ValueError(msg, src) + res = m.groupdict() + extras = cls._parse_extras(res['extras']) + attrs = res['attr'].split('.') if res['attr'] else () + return cls(res['name'], res['module'], attrs, extras, dist) + + @classmethod + def _parse_extras(cls, extras_spec): + if not extras_spec: + return () + req = Requirement.parse('x' + extras_spec) + if req.specs: + raise ValueError() + return req.extras + + @classmethod + def parse_group(cls, group, lines, dist=None): + """Parse an entry point group""" + if not MODULE(group): + raise ValueError("Invalid group name", group) + this = {} + for line in yield_lines(lines): + ep = cls.parse(line, dist) + if ep.name in this: + raise ValueError("Duplicate entry point", group, ep.name) + this[ep.name] = ep + return this + + @classmethod + def parse_map(cls, data, dist=None): + """Parse a map of entry point groups""" + if isinstance(data, dict): + data = data.items() + else: + data = split_sections(data) + maps = {} + for group, lines in data: + if group is None: + if not lines: + continue + raise ValueError("Entry points must be listed in groups") + group = group.strip() + if group in maps: + raise ValueError("Duplicate group name", group) + maps[group] = cls.parse_group(group, lines, dist) + 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 + the value of the Version field, if present, or None otherwise. + """ + def is_version_line(line): + return line.lower().startswith('version:') + version_lines = filter(is_version_line, lines) + line = next(iter(version_lines), '') + _, _, value = line.partition(':') + return safe_version(value.strip()) or None + + +class Distribution: + """Wrap an actual or potential sys.path entry w/metadata""" + PKG_INFO = 'PKG-INFO' + + def __init__( + self, location=None, metadata=None, project_name=None, + version=None, py_version=PY_MAJOR, platform=None, + precedence=EGG_DIST): + self.project_name = safe_name(project_name or 'Unknown') + if version is not None: + self._version = safe_version(version) + self.py_version = py_version + self.platform = platform + self.location = location + self.precedence = precedence + self._provider = metadata or empty_provider + + @classmethod + def from_location(cls, location, basename, metadata=None, **kw): + project_name, version, py_version, platform = [None] * 4 + basename, ext = os.path.splitext(basename) + if ext.lower() in _distributionImpl: + cls = _distributionImpl[ext.lower()] + + match = EGG_NAME(basename) + if match: + project_name, version, py_version, platform = match.group( + 'name', 'ver', 'pyver', 'plat' + ) + return cls( + location, metadata, project_name=project_name, version=version, + py_version=py_version, platform=platform, **kw + )._reload_version() + + def _reload_version(self): + return self + + @property + def hashcmp(self): + return ( + self.parsed_version, + self.precedence, + self.key, + _remove_md5_fragment(self.location), + self.py_version or '', + self.platform or '', + ) + + def __hash__(self): + return hash(self.hashcmp) + + def __lt__(self, other): + return self.hashcmp < other.hashcmp + + def __le__(self, other): + return self.hashcmp <= other.hashcmp + + def __gt__(self, other): + return self.hashcmp > other.hashcmp + + def __ge__(self, other): + return self.hashcmp >= other.hashcmp + + def __eq__(self, other): + if not isinstance(other, self.__class__): + # It's not a Distribution, so they are not equal + return False + return self.hashcmp == other.hashcmp + + def __ne__(self, other): + return not self == other + + # These properties have to be lazy so that we don't have to load any + # metadata until/unless it's actually needed. (i.e., some distributions + # may not know their name or version without loading PKG-INFO) + + @property + def key(self): + try: + return self._key + except AttributeError: + self._key = key = self.project_name.lower() + return key + + @property + def parsed_version(self): + if not hasattr(self, "_parsed_version"): + self._parsed_version = parse_version(self.version) + + return self._parsed_version + + def _warn_legacy_version(self): + LV = packaging.version.LegacyVersion + is_legacy = isinstance(self._parsed_version, LV) + if not is_legacy: + return + + # While an empty version is technically a legacy version and + # is not a valid PEP 440 version, it's also unlikely to + # actually come from someone and instead it is more likely that + # it comes from setuptools attempting to parse a filename and + # including it in the list. So for that we'll gate this warning + # on if the version is anything at all or not. + if not self.version: + return + + tmpl = textwrap.dedent(""" + '{project_name} ({version})' is being parsed as a legacy, + non PEP 440, + version. You may find odd behavior and sort order. + In particular it will be sorted as less than 0.0. It + is recommended to migrate to PEP 440 compatible + versions. + """).strip().replace('\n', ' ') + + warnings.warn(tmpl.format(**vars(self)), PEP440Warning) + + @property + def version(self): + try: + return self._version + except AttributeError: + version = _version_from_file(self._get_metadata(self.PKG_INFO)) + if version is None: + tmpl = "Missing 'Version:' header and/or %s file" + raise ValueError(tmpl % self.PKG_INFO, self) + return version + + @property + def _dep_map(self): + """ + A map of extra to its list of (direct) requirements + for this distribution, including the null extra. + """ + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._filter_extras(self._build_dep_map()) + return self.__dep_map + + @staticmethod + def _filter_extras(dm): + """ + Given a mapping of extras to dependencies, strip off + environment markers and filter out any dependencies + not matching the markers. + """ + for extra in list(filter(None, dm)): + new_extra = extra + reqs = dm.pop(extra) + new_extra, _, marker = extra.partition(':') + fails_marker = marker and ( + invalid_marker(marker) + or not evaluate_marker(marker) + ) + if fails_marker: + reqs = [] + new_extra = safe_extra(new_extra) or None + + dm.setdefault(new_extra, []).extend(reqs) + return dm + + def _build_dep_map(self): + dm = {} + for name in 'requires.txt', 'depends.txt': + for extra, reqs in split_sections(self._get_metadata(name)): + dm.setdefault(extra, []).extend(parse_requirements(reqs)) + return dm + + def requires(self, extras=()): + """List of Requirements needed for this distro if `extras` are used""" + dm = self._dep_map + deps = [] + deps.extend(dm.get(None, ())) + for ext in extras: + try: + deps.extend(dm[safe_extra(ext)]) + except KeyError: + raise UnknownExtra( + "%s has no such extra feature %r" % (self, ext) + ) + return deps + + def _get_metadata(self, name): + if self.has_metadata(name): + for line in self.get_metadata_lines(name): + yield line + + def activate(self, path=None, replace=False): + """Ensure distribution is importable on `path` (default=sys.path)""" + if path is None: + path = sys.path + self.insert_on(path, replace=replace) + if path is sys.path: + fixup_namespace_packages(self.location) + for pkg in self._get_metadata('namespace_packages.txt'): + if pkg in sys.modules: + declare_namespace(pkg) + + def egg_name(self): + """Return what this distribution's standard .egg filename should be""" + filename = "%s-%s-py%s" % ( + to_filename(self.project_name), to_filename(self.version), + self.py_version or PY_MAJOR + ) + + if self.platform: + filename += '-' + self.platform + return filename + + def __repr__(self): + if self.location: + return "%s (%s)" % (self, self.location) + else: + return str(self) + + def __str__(self): + try: + version = getattr(self, 'version', None) + except ValueError: + version = None + version = version or "[unknown version]" + return "%s %s" % (self.project_name, version) + + def __getattr__(self, attr): + """Delegate all unrecognized public attributes to .metadata provider""" + if attr.startswith('_'): + raise AttributeError(attr) + return getattr(self._provider, attr) + + def __dir__(self): + return list( + set(super(Distribution, self).__dir__()) + | set( + attr for attr in self._provider.__dir__() + if not attr.startswith('_') + ) + ) + + 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( + _normalize_cached(filename), os.path.basename(filename), metadata, + **kw + ) + + def as_requirement(self): + """Return a ``Requirement`` that matches this distribution exactly""" + if isinstance(self.parsed_version, packaging.version.Version): + spec = "%s==%s" % (self.project_name, self.parsed_version) + else: + spec = "%s===%s" % (self.project_name, self.parsed_version) + + return Requirement.parse(spec) + + def load_entry_point(self, group, name): + """Return the `name` entry point of `group` or raise ImportError""" + ep = self.get_entry_info(group, name) + if ep is None: + raise ImportError("Entry point %r not found" % ((group, name),)) + return ep.load() + + def get_entry_map(self, group=None): + """Return the entry point map for `group`, or the full entry map""" + try: + ep_map = self._ep_map + except AttributeError: + ep_map = self._ep_map = EntryPoint.parse_map( + self._get_metadata('entry_points.txt'), self + ) + if group is not None: + return ep_map.get(group, {}) + return ep_map + + def get_entry_info(self, group, name): + """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): + """Ensure self.location is on path + + If replace=False (default): + - If location is already in path anywhere, do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent. + - Else: add to the end of path. + If replace=True: + - If location is already on path anywhere (not eggs) + or higher priority than its parent (eggs) + do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent, + removing any lower-priority entries. + - Else: add it to the front of path. + """ + + loc = loc or self.location + if not loc: + return + + nloc = _normalize_cached(loc) + bdir = os.path.dirname(nloc) + npath = [(p and _normalize_cached(p) or p) for p in path] + + for p, item in enumerate(npath): + if item == nloc: + if replace: + break + else: + # don't modify path (even removing duplicates) if + # found and not replace + return + elif item == bdir and self.precedence == EGG_DIST: + # if it's an .egg, give it precedence over its directory + # UNLESS it's already been added to sys.path and replace=False + if (not replace) and nloc in npath[p:]: + return + if path is sys.path: + self.check_version_conflict() + path.insert(p, loc) + npath.insert(p, nloc) + break + else: + if path is sys.path: + self.check_version_conflict() + if replace: + path.insert(0, loc) + else: + path.append(loc) + return + + # p is the spot where we found or inserted loc; now remove duplicates + while True: + try: + np = npath.index(nloc, p + 1) + except ValueError: + break + else: + del npath[np], path[np] + # ha! + p = np + + return + + def check_version_conflict(self): + if self.key == 'setuptools': + # ignore the inevitable setuptools self-conflicts :( + return + + nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt')) + loc = normalize_path(self.location) + for modname in self._get_metadata('top_level.txt'): + if (modname not in sys.modules or modname in nsp + or modname in _namespace_packages): + continue + if modname in ('pkg_resources', 'setuptools', 'site'): + continue + fn = getattr(sys.modules[modname], '__file__', None) + if fn and (normalize_path(fn).startswith(loc) or + fn.startswith(self.location)): + continue + issue_warning( + "Module %s was already imported from %s, but %s is being added" + " to sys.path" % (modname, fn, self.location), + ) + + def has_version(self): + try: + self.version + except ValueError: + issue_warning("Unbuilt egg for " + repr(self)) + return False + return True + + def clone(self, **kw): + """Copy this distribution, substituting in any changed keyword args""" + names = 'project_name version py_version platform location precedence' + for attr in names.split(): + kw.setdefault(attr, getattr(self, attr, None)) + kw.setdefault('metadata', self._provider) + return self.__class__(**kw) + + @property + def extras(self): + return [dep for dep in self._dep_map if dep] + + +class EggInfoDistribution(Distribution): + def _reload_version(self): + """ + Packages installed by distutils (e.g. numpy or scipy), + which uses an old safe_version, and so + their version numbers can get mangled when + converted to filenames (e.g., 1.11.0.dev0+2329eae to + 1.11.0.dev0_2329eae). These distributions will not be + parsed properly + downstream by Distribution and safe_version, so + take an extra step and try to get the version number from + the metadata file itself instead of the filename. + """ + md_version = _version_from_file(self._get_metadata(self.PKG_INFO)) + if md_version: + self._version = md_version + return self + + +class DistInfoDistribution(Distribution): + """ + Wrap an actual or potential sys.path entry + w/metadata, .dist-info style. + """ + PKG_INFO = 'METADATA' + EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") + + @property + def _parsed_pkg_info(self): + """Parse and cache metadata""" + try: + return self._pkg_info + except AttributeError: + metadata = self.get_metadata(self.PKG_INFO) + self._pkg_info = email.parser.Parser().parsestr(metadata) + return self._pkg_info + + @property + def _dep_map(self): + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._compute_dependencies() + return self.__dep_map + + def _compute_dependencies(self): + """Recompute this distribution's dependencies.""" + dm = self.__dep_map = {None: []} + + reqs = [] + # Including any condition expressions + for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: + reqs.extend(parse_requirements(req)) + + def reqs_for_extra(extra): + for req in reqs: + if not req.marker or req.marker.evaluate({'extra': extra}): + yield req + + common = frozenset(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) + + return dm + + +_distributionImpl = { + '.egg': Distribution, + '.egg-info': EggInfoDistribution, + '.dist-info': DistInfoDistribution, +} + + +def issue_warning(*args, **kw): + level = 1 + g = globals() + try: + # find the first stack frame that is *not* code in + # the pkg_resources module, to use for the warning + while sys._getframe(level).f_globals is g: + level += 1 + except ValueError: + pass + 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` + + `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)) + + 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 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)) + self.unsafe_name = self.name + project_name = safe_name(self.name) + self.project_name, self.key = project_name, project_name.lower() + self.specs = [ + (spec.operator, spec.version) for spec in self.specifier] + self.extras = tuple(map(safe_extra, self.extras)) + self.hashCmp = ( + self.key, + self.specifier, + frozenset(self.extras), + str(self.marker) if self.marker else None, + ) + self.__hash = hash(self.hashCmp) + + def __eq__(self, other): + return ( + isinstance(other, Requirement) and + self.hashCmp == other.hashCmp + ) + + def __ne__(self, other): + return not self == other + + def __contains__(self, item): + if isinstance(item, Distribution): + if item.key != self.key: + return False + + item = item.version + + # Allow prereleases always in order to match the previous behavior of + # this method. In the future this should be smarter and follow PEP 440 + # more accurately. + return self.specifier.contains(item, prereleases=True) + + def __hash__(self): + return self.__hash + + def __repr__(self): + return "Requirement.parse(%r)" % str(self) + + @staticmethod + def parse(s): + req, = parse_requirements(s) + return req + + +def _always_object(classes): + """ + Ensure object appears in the mro even + for old-style classes. + """ + if object not in classes: + return classes + (object,) + return classes + + +def _find_adapter(registry, ob): + """Return an adapter factory for `ob` from `registry`""" + types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob)))) + for t in types: + if t in registry: + return registry[t] + + +def ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + py31compat.makedirs(dirname, exist_ok=True) + + +def _bypass_ensure_directory(path): + """Sandbox-bypassing version of ensure_directory()""" + if not WRITE_SUPPORT: + raise IOError('"os.mkdir" not supported on this platform.') + dirname, filename = split(path) + if dirname and filename and not isdir(dirname): + _bypass_ensure_directory(dirname) + try: + mkdir(dirname, 0o755) + except FileExistsError: + pass + + +def split_sections(s): + """Split a string or iterable thereof into (section, content) pairs + + Each ``section`` is a stripped version of the section header ("[section]") + and each ``content`` is a list of stripped lines excluding blank lines and + comment-only lines. If there are any such lines before the first section + header, they're returned in a first ``section`` of ``None``. + """ + section = None + content = [] + for line in yield_lines(s): + if line.startswith("["): + if line.endswith("]"): + if section or content: + yield section, content + section = line[1:-1].strip() + content = [] + else: + raise ValueError("Invalid section heading", line) + else: + content.append(line) + + # wrap up last segment + yield section, content + + +def _mkstemp(*args, **kw): + old_open = os.open + try: + # temporarily bypass sandboxing + os.open = os_open + return tempfile.mkstemp(*args, **kw) + finally: + # and then put it back + os.open = old_open + + +# Silence the PEP440Warning by default, so that end users don't get hit by it +# randomly just because they use pkg_resources. We want to append the rule +# because we want earlier uses of filterwarnings to take precedence over this +# one. +warnings.filterwarnings("ignore", category=PEP440Warning, append=True) + + +# from jaraco.functools 1.3 +def _call_aside(f, *args, **kwargs): + f(*args, **kwargs) + return f + + +@_call_aside +def _initialize(g=globals()): + "Set up global resource manager (deliberately not state-saved)" + manager = ResourceManager() + g['_manager'] = manager + g.update( + (name, getattr(manager, name)) + for name in dir(manager) + if not name.startswith('_') + ) + + +@_call_aside +def _initialize_master_working_set(): + """ + Prepare the master working set and make the ``require()`` + API available. + + This function has explicit effects on the global state + of pkg_resources. It is intended to be invoked once at + the initialization of this module. + + Invocation by other packages is unsupported and done + at their own risk. + """ + working_set = WorkingSet._build_master() + _declare_state('object', working_set=working_set) + + require = working_set.require + iter_entry_points = working_set.iter_entry_points + add_activation_listener = working_set.subscribe + run_script = working_set.run_script + # backward compatibility + run_main = run_script + # Activate all distributions already on sys.path with replace=False and + # ensure that all distributions added to the working set in the future + # (e.g. by calling ``require()``) will get activated as well, + # with higher priority (replace=True). + tuple( + dist.activate(replace=False) + for dist in working_set + ) + add_activation_listener( + lambda dist: dist.activate(replace=True), + existing=False, + ) + working_set.entries = [] + # 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/py31compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/py31compat.py new file mode 100644 index 0000000..a2d3007 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pkg_resources/py31compat.py @@ -0,0 +1,23 @@ +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/__init__.py new file mode 100644 index 0000000..a41f65d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/__init__.py @@ -0,0 +1,127 @@ +# Copyright (c) 2012 Giorgos 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 + +from collections import deque +from datetime import timedelta +from math import ceil +from sys import stderr +from time import time + + +__version__ = '1.4' + + +class Infinite(object): + file = stderr + sma_window = 10 # Simple Moving Average window + + def __init__(self, *args, **kwargs): + self.index = 0 + self.start_ts = time() + self.avg = 0 + self._ts = self.start_ts + self._xput = deque(maxlen=self.sma_window) + for key, val in kwargs.items(): + setattr(self, key, val) + + def __getitem__(self, key): + if key.startswith('_'): + return None + return getattr(self, key, None) + + @property + def elapsed(self): + return int(time() - self.start_ts) + + @property + def elapsed_td(self): + return timedelta(seconds=self.elapsed) + + def update_avg(self, n, dt): + if n > 0: + self._xput.append(dt / n) + self.avg = sum(self._xput) / len(self._xput) + + def update(self): + pass + + def start(self): + pass + + def finish(self): + pass + + def next(self, n=1): + now = time() + dt = now - self._ts + self.update_avg(n, dt) + self._ts = now + self.index = self.index + n + self.update() + + def iter(self, it): + try: + for x in it: + yield x + self.next() + finally: + 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): + 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 + + try: + for x in it: + yield x + self.next() + finally: + self.finish() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/bar.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/bar.py new file mode 100644 index 0000000..025e61c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/bar.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 Giorgos 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 .helpers import WritelnMixin + + +class Bar(WritelnMixin, Progress): + width = 32 + message = '' + suffix = '%(index)d/%(max)d' + bar_prefix = ' |' + bar_suffix = '| ' + empty_fill = ' ' + fill = '#' + hide_cursor = True + + def update(self): + filled_length = int(self.width * self.progress) + empty_length = self.width - filled_length + + message = self.message % self + bar = self.fill * filled_length + 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 = self.phases[-1] * nfull + 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/counter.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/counter.py new file mode 100644 index 0000000..6b45a1e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/counter.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 Giorgos 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 +from .helpers import WriteMixin + + +class Counter(WriteMixin, Infinite): + message = '' + hide_cursor = True + + def update(self): + self.write(str(self.index)) + + +class Countdown(WriteMixin, Progress): + hide_cursor = True + + def update(self): + self.write(str(self.remaining)) + + +class Stack(WriteMixin, Progress): + phases = (' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█') + hide_cursor = True + + def update(self): + nphases = len(self.phases) + i = min(nphases - 1, int(self.progress * nphases)) + self.write(self.phases[i]) + + +class Pie(Stack): + phases = ('○', '◔', '◑', '◕', '●') diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/helpers.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/helpers.py new file mode 100644 index 0000000..0cde44e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/helpers.py @@ -0,0 +1,91 @@ +# Copyright (c) 2012 Giorgos 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 print_function + + +HIDE_CURSOR = '\x1b[?25l' +SHOW_CURSOR = '\x1b[?25h' + + +class WriteMixin(object): + hide_cursor = False + + def __init__(self, message=None, **kwargs): + super(WriteMixin, self).__init__(**kwargs) + self._width = 0 + if message: + self.message = message + + if self.file and self.file.isatty(): + if self.hide_cursor: + print(HIDE_CURSOR, end='', file=self.file) + print(self.message, end='', file=self.file) + self.file.flush() + + def write(self, s): + if self.file and self.file.isatty(): + b = '\b' * self._width + c = s.ljust(self._width) + print(b + c, end='', file=self.file) + self._width = max(self._width, len(s)) + self.file.flush() + + def finish(self): + if self.file and self.file.isatty() and self.hide_cursor: + print(SHOW_CURSOR, end='', file=self.file) + + +class WritelnMixin(object): + hide_cursor = False + + def __init__(self, message=None, **kwargs): + super(WritelnMixin, self).__init__(**kwargs) + if message: + self.message = message + + if self.file and self.file.isatty() and self.hide_cursor: + print(HIDE_CURSOR, end='', file=self.file) + + def clearln(self): + if self.file and self.file.isatty(): + print('\r\x1b[K', end='', file=self.file) + + def writeln(self, line): + if self.file and self.file.isatty(): + self.clearln() + print(line, end='', file=self.file) + self.file.flush() + + def finish(self): + if self.file and self.file.isatty(): + print(file=self.file) + if self.hide_cursor: + print(SHOW_CURSOR, end='', file=self.file) + + +from signal import signal, SIGINT +from sys import exit + + +class SigIntMixin(object): + """Registers a signal handler that calls finish on SIGINT""" + + def __init__(self, *args, **kwargs): + super(SigIntMixin, self).__init__(*args, **kwargs) + signal(SIGINT, self._sigint_handler) + + def _sigint_handler(self, signum, frame): + self.finish() + exit(0) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/spinner.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/spinner.py new file mode 100644 index 0000000..464c7b2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/progress/spinner.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 Giorgos 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 +from .helpers import WriteMixin + + +class Spinner(WriteMixin, Infinite): + message = '' + phases = ('-', '\\', '|', '/') + hide_cursor = True + + def update(self): + i = self.index % len(self.phases) + self.write(self.phases[i]) + + +class PieSpinner(Spinner): + phases = ['◷', '◶', '◵', '◴'] + + +class MoonSpinner(Spinner): + phases = ['◑', '◒', '◐', '◓'] + + +class LineSpinner(Spinner): + phases = ['⎺', '⎻', '⎼', '⎽', '⎼', '⎻'] + +class PixelSpinner(Spinner): + phases = ['⣾','⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽'] diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pyparsing.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pyparsing.py new file mode 100644 index 0000000..bea4d9c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pyparsing.py @@ -0,0 +1,6452 @@ +#-*- 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` + - 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.3.1" +__versionTime__ = "09 Jan 2019 23:26 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 + +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 +except ImportError: + # Python 2.7 + from collections import Iterable + from collections import MutableMapping + +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 + + +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + +__all__ = [ +'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', +] + +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) + +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 ): + return "%s (at char %d), (line:%d, col:%d)" % \ + ( self.msg, 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.frame + + 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 += 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 "" + + if name in self.__tokdict: + if name not in self.__accumNames: + return self.__tokdict[name][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[name] ]) + else: + 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='', depth=0, full=True): + """ + 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' + out.append( indent+_ustr(self.asList()) ) + 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,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,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())) + +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: + del tb + + 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 + + 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 hasattr(self,"exception"): + self.exception.msg = self.errmsg + 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") + """ + 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 + 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. + + 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] + """ + 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) + """ + msg = kwargs.get("message", "failed user-defined condition") + exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException + for fn in fns: + fn = _trim_arity(fn) + def pa(s,l,t): + if not bool(fn(s,l,t)): + raise exc_type(s,l,msg) + self.parseAction.append(pa) + 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 ): + 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[0] ): + self.debugActions[0]( instring, loc, self ) + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = preloc + try: + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + except ParseBaseException as err: + #~ print ("Exception raised:", err) + if self.debugActions[2]: + self.debugActions[2]( 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 ParseBaseException as err: + #~ print "Exception raised in user parse action:", err + if (self.debugActions[2] ): + self.debugActions[2]( 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[1] ): + self.debugActions[1]( 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. + + 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, clears out pyparsing internal stack trace + 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, clears out pyparsing internal stack trace + 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, clears out pyparsing internal stack trace + 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, clears out pyparsing internal stack trace + 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', '!'] + """ + if isinstance( other, basestring ): + other = ParserElement._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 isinstance( other, basestring ): + other = ParserElement._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 = ParserElement._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 = ParserElement._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 isinstance(other,int): + minElements, optElements = other,0 + elif isinstance(other,tuple): + 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 isinstance( other, basestring ): + other = ParserElement._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 = ParserElement._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 = ParserElement._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 = ParserElement._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 = ParserElement._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 = ParserElement._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 __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=[] ): + """ + 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, clears out pyparsing internal stack trace + raise exc + + def __eq__(self,other): + if isinstance(other, ParserElement): + return self is other or vars(self) == vars(other) + elif isinstance(other, basestring): + return self.matches(other) + else: + return super(ParserElement,self)==other + + def __ne__(self,other): + return not (self == other) + + def __hash__(self): + return hash(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): + """ + 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 + + 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) + allResults = [] + comments = [] + success = True + 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'.join(comments), t] + comments = [] + try: + # convert newline marks to actual newlines, and strip leading BOM if present + t = t.replace(r'\n','\n').lstrip('\ufeff') + result = self.parseString(t, parseAll=parseAll) + out.append(result.dump(full=fullDump)) + success = success and not failureTests + if postParse is not None: + try: + pp_value = postParse(t, result) + if pp_value is not None: + out.append(str(pp_value)) + except Exception as e: + out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e)) + 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 + + if printResults: + if fullDump: + out.append('') + print('\n'.join(out)) + + allResults.append((t, result)) + + return success, allResults + + +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: this routine gets called a *lot* + # if this is a single character match string and the first character matches, + # short-circuit as quickly as possible, and avoid calling startswith + #~ @profile + def parseImpl( self, instring, loc, doActions=True ): + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) ): + return loc+self.matchLen, 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 and + (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], self.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'] = self.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: + 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 + + def parseImpl( self, instring, loc, doActions=True ): + if self.re: + result = self.re.match(instring,loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + return loc, result.group() + + if not(instring[ loc ] 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 + if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + if 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 Char(Word): + """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): + super(Char, self).__init__(charset, exact=1) + self.reString = "[%s]" % _escapeRegexRangeChars(self.initCharsOrig) + self.re = re.compile( self.reString ) + + +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. + + 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})") + """ + compiledREtype = type(re.compile("[A-Z]")) + 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 isinstance(pattern, Regex.compiledREtype): + self.re = pattern + self.pattern = \ + self.reString = str(pattern) + self.flags = flags + + else: + raise ValueError("Regex may only be constructed with a string or a compiled RE object") + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + self.asGroupList = asGroupList + self.asMatch = asMatch + + 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() + if self.asMatch: + ret = result + elif self.asGroupList: + ret = result.groups() + else: + ret = ParseResults(result.group()) + d = result.groupdict() + if d: + for k, v in d.items(): + ret[k] = v + 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): + """ + 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 + 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\00A0': '<NBSP>', + 'u\1680': '<OGHAM_SPACE_MARK>', + 'u\180E': '<MONGOLIAN_VOWEL_SEPARATOR>', + 'u\2000': '<EN_QUAD>', + 'u\2001': '<EM_QUAD>', + 'u\2002': '<EN_SPACE>', + 'u\2003': '<EM_SPACE>', + 'u\2004': '<THREE-PER-EM_SPACE>', + 'u\2005': '<FOUR-PER-EM_SPACE>', + 'u\2006': '<SIX-PER-EM_SPACE>', + 'u\2007': '<FIGURE_SPACE>', + 'u\2008': '<PUNCTUATION_SPACE>', + 'u\2009': '<THIN_SPACE>', + 'u\200A': '<HAIR_SPACE>', + 'u\200B': '<ZERO_WIDTH_SPACE>', + 'u\202F': '<NNBSP>', + 'u\205F': '<MMSP>', + 'u\3000': '<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 not(instring[ loc ] 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): + """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 = [ ParserElement._literalStringClass( exprs ) ] + elif isinstance( exprs, Iterable ): + exprs = list(exprs) + # if sequence of strings provided, wrap with Literal + if all(isinstance(expr, basestring) for expr in exprs): + exprs = map(ParserElement._literalStringClass, exprs) + self.exprs = list(exprs) + else: + try: + self.exprs = list( exprs ) + except TypeError: + self.exprs = [ exprs ] + self.callPreparse = False + + def __getitem__( self, i ): + return self.exprs[i] + + 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 setResultsName( self, name, listAllMatches=False ): + ret = super(ParseExpression,self).setResultsName(name,listAllMatches) + return ret + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[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 + +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 ): + 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): + 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 = ParserElement._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() + 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: + matches.sort(key=lambda x: -x[0]) + for _,e in matches: + try: + return e._parse( instring, loc, doActions ) + except ParseException as err: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + + 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 = ParserElement._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 ) + + +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) + # self.saveAsList = any(e.saveAsList for e in self.exprs) + else: + self.mayReturnEmpty = True + + def streamline(self): + super(MatchFirst, self).streamline() + 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 = ParserElement._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 ) + + +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)] + 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(ParserElement._literalStringClass, Token): + expr = ParserElement._literalStringClass(expr) + else: + expr = ParserElement._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=[] ): + 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 ): + _, 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 + + 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[:loc] + last_expr = ParseException(instring, loc, self.errmsg) + for offset in range(1, min(loc, self.retreat+1)): + try: + _, ret = test_expr._parse(instring_slice, loc-offset) + except ParseBaseException as pbe: + last_expr = pbe + else: + break + else: + raise last_expr + # return empty list of tokens, but preserve any defined results names + del ret[:] + 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 = ParserElement._literalStringClass(ender) + self.not_ender = ~ender if ender is not None else None + + 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 + +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 "" + +_optionalNotMatched = _NullToken() +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) + """ + 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 _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 = ParserElement._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 = ParserElement._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=[] ): + 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 + return self.__class__.__name__ + ": ..." + + # stubbed out for now - creates awful memory and perf issues + self._revertClass = self.__class__ + self.__class__ = _ForwardNoRecurse + try: + if self.expr is not None: + retString = _ustr(self.expr) + else: + retString = "None" + finally: + self.__class__ = self._revertClass + return self.__class__.__name__ + ": " + retString + + def copy(self): + if self.expr is not None: + return super(Forward,self).copy() + else: + ret = Forward() + ret <<= self + return ret + +class _ForwardNoRecurse(Forward): + def __str__( self ): + return "..." + +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 = expr.saveAsList + + 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 ): + """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 if + creating a :class:`Regex` raises an exception) + + 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 caseless: + isequal = ( lambda a,b: a.upper() == b.upper() ) + masks = ( lambda a,b: b.upper().startswith(a.upper()) ) + parseElementClass = CaselessLiteral + else: + isequal = ( lambda a,b: a == b ) + masks = ( lambda a,b: b.startswith(a) ) + parseElementClass = 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() + + 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) + cur = other + break + else: + i += 1 + + if not caseless 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).setParseAction(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): + """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("<") + tagStr("tag") + \ + Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + else: + printablesLessRAbrack = "".join(c for c in printables if c not in ">") + tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) + openTag = Suppress("<") + tagStr("tag") + \ + Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ + Optional( Suppress("=") + tagAttrValue ) ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + closeTag = Combine(_L("</") + tagStr + ">") + + openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname) + closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname) + openTag.tag = resname + closeTag.tag = resname + 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 <div> 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 + 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 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']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(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 < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + 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*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\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, "العربية", pyparsing_unicode.Arabic) + setattr(pyparsing_unicode, "中文", pyparsing_unicode.Chinese) + setattr(pyparsing_unicode, "кириллица", pyparsing_unicode.Cyrillic) + setattr(pyparsing_unicode, "Ελληνικά", pyparsing_unicode.Greek) + setattr(pyparsing_unicode, "עִברִית", pyparsing_unicode.Hebrew) + setattr(pyparsing_unicode, "日本語", pyparsing_unicode.Japanese) + setattr(pyparsing_unicode.Japanese, "漢字", pyparsing_unicode.Japanese.Kanji) + setattr(pyparsing_unicode.Japanese, "カタカナ", pyparsing_unicode.Japanese.Katakana) + setattr(pyparsing_unicode.Japanese, "ひらがな", pyparsing_unicode.Japanese.Hiragana) + setattr(pyparsing_unicode, "한국어", pyparsing_unicode.Korean) + setattr(pyparsing_unicode, "ไทย", pyparsing_unicode.Thai) + setattr(pyparsing_unicode, "देवनागरी", pyparsing_unicode.Devanagari) + + +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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/__init__.py new file mode 100644 index 0000000..8ed060f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/__init__.py @@ -0,0 +1,4 @@ +from .core import TomlError +from .parser import load, loads +from .test import translate_to_test +from .writer import dump, dumps \ No newline at end of file diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/core.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/core.py new file mode 100644 index 0000000..c182734 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/core.py @@ -0,0 +1,13 @@ +class TomlError(RuntimeError): + def __init__(self, message, line, col, filename): + RuntimeError.__init__(self, message, line, col, filename) + self.message = message + self.line = line + self.col = col + self.filename = filename + + def __str__(self): + return '{}({}, {}): {}'.format(self.filename, self.line, self.col, self.message) + + def __repr__(self): + return 'TomlError({!r}, {!r}, {!r}, {!r})'.format(self.message, self.line, self.col, self.filename) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/parser.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/parser.py new file mode 100644 index 0000000..3493aa6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/parser.py @@ -0,0 +1,341 @@ +import string, re, sys, datetime +from .core import TomlError +from .utils import rfc3339_re, parse_rfc3339_re + +if sys.version_info[0] == 2: + _chr = unichr +else: + _chr = chr + +def load(fin, translate=lambda t, x, v: v, object_pairs_hook=dict): + return loads(fin.read(), translate=translate, object_pairs_hook=object_pairs_hook, filename=getattr(fin, 'name', repr(fin))) + +def loads(s, filename='<string>', translate=lambda t, x, v: v, object_pairs_hook=dict): + if isinstance(s, bytes): + s = s.decode('utf-8') + + s = s.replace('\r\n', '\n') + + root = object_pairs_hook() + tables = object_pairs_hook() + scope = root + + src = _Source(s, filename=filename) + ast = _p_toml(src, object_pairs_hook=object_pairs_hook) + + def error(msg): + raise TomlError(msg, pos[0], pos[1], filename) + + def process_value(v, object_pairs_hook): + kind, text, value, pos = v + if kind == 'str' and value.startswith('\n'): + value = value[1:] + if kind == 'array': + if value and any(k != value[0][0] for k, t, v, p in value[1:]): + error('array-type-mismatch') + value = [process_value(item, object_pairs_hook=object_pairs_hook) for item in value] + elif kind == 'table': + value = object_pairs_hook([(k, process_value(value[k], object_pairs_hook=object_pairs_hook)) for k in value]) + return translate(kind, text, value) + + for kind, value, pos in ast: + if kind == 'kv': + k, v = value + if k in scope: + error('duplicate_keys. Key "{0}" was used more than once.'.format(k)) + scope[k] = process_value(v, object_pairs_hook=object_pairs_hook) + else: + is_table_array = (kind == 'table_array') + cur = tables + for name in value[:-1]: + if isinstance(cur.get(name), list): + d, cur = cur[name][-1] + else: + d, cur = cur.setdefault(name, (None, object_pairs_hook())) + + scope = object_pairs_hook() + name = value[-1] + if name not in cur: + if is_table_array: + cur[name] = [(scope, object_pairs_hook())] + else: + cur[name] = (scope, object_pairs_hook()) + elif isinstance(cur[name], list): + if not is_table_array: + error('table_type_mismatch') + cur[name].append((scope, object_pairs_hook())) + else: + if is_table_array: + error('table_type_mismatch') + old_scope, next_table = cur[name] + if old_scope is not None: + error('duplicate_tables') + cur[name] = (scope, next_table) + + def merge_tables(scope, tables): + if scope is None: + scope = object_pairs_hook() + for k in tables: + if k in scope: + error('key_table_conflict') + v = tables[k] + if isinstance(v, list): + scope[k] = [merge_tables(sc, tbl) for sc, tbl in v] + else: + scope[k] = merge_tables(v[0], v[1]) + return scope + + return merge_tables(root, tables) + +class _Source: + def __init__(self, s, filename=None): + self.s = s + self._pos = (1, 1) + self._last = None + self._filename = filename + self.backtrack_stack = [] + + def last(self): + return self._last + + def pos(self): + return self._pos + + def fail(self): + return self._expect(None) + + def consume_dot(self): + if self.s: + self._last = self.s[0] + self.s = self[1:] + self._advance(self._last) + return self._last + return None + + def expect_dot(self): + return self._expect(self.consume_dot()) + + def consume_eof(self): + if not self.s: + self._last = '' + return True + return False + + def expect_eof(self): + return self._expect(self.consume_eof()) + + def consume(self, s): + if self.s.startswith(s): + self.s = self.s[len(s):] + self._last = s + self._advance(s) + return True + return False + + def expect(self, s): + return self._expect(self.consume(s)) + + def consume_re(self, re): + m = re.match(self.s) + if m: + self.s = self.s[len(m.group(0)):] + self._last = m + self._advance(m.group(0)) + return m + return None + + def expect_re(self, re): + return self._expect(self.consume_re(re)) + + def __enter__(self): + self.backtrack_stack.append((self.s, self._pos)) + + def __exit__(self, type, value, traceback): + if type is None: + self.backtrack_stack.pop() + else: + self.s, self._pos = self.backtrack_stack.pop() + return type == TomlError + + def commit(self): + self.backtrack_stack[-1] = (self.s, self._pos) + + def _expect(self, r): + if not r: + raise TomlError('msg', self._pos[0], self._pos[1], self._filename) + return r + + def _advance(self, s): + suffix_pos = s.rfind('\n') + if suffix_pos == -1: + self._pos = (self._pos[0], self._pos[1] + len(s)) + else: + self._pos = (self._pos[0] + s.count('\n'), len(s) - suffix_pos) + +_ews_re = re.compile(r'(?:[ \t]|#[^\n]*\n|#[^\n]*\Z|\n)*') +def _p_ews(s): + s.expect_re(_ews_re) + +_ws_re = re.compile(r'[ \t]*') +def _p_ws(s): + s.expect_re(_ws_re) + +_escapes = { 'b': '\b', 'n': '\n', 'r': '\r', 't': '\t', '"': '"', + '\\': '\\', 'f': '\f' } + +_basicstr_re = re.compile(r'[^"\\\000-\037]*') +_short_uni_re = re.compile(r'u([0-9a-fA-F]{4})') +_long_uni_re = re.compile(r'U([0-9a-fA-F]{8})') +_escapes_re = re.compile(r'[btnfr\"\\]') +_newline_esc_re = re.compile('\n[ \t\n]*') +def _p_basicstr_content(s, content=_basicstr_re): + res = [] + while True: + res.append(s.expect_re(content).group(0)) + if not s.consume('\\'): + break + if s.consume_re(_newline_esc_re): + pass + elif s.consume_re(_short_uni_re) or s.consume_re(_long_uni_re): + v = int(s.last().group(1), 16) + if 0xd800 <= v < 0xe000: + s.fail() + res.append(_chr(v)) + else: + s.expect_re(_escapes_re) + res.append(_escapes[s.last().group(0)]) + return ''.join(res) + +_key_re = re.compile(r'[0-9a-zA-Z-_]+') +def _p_key(s): + with s: + s.expect('"') + r = _p_basicstr_content(s, _basicstr_re) + s.expect('"') + return r + if s.consume('\''): + if s.consume('\'\''): + r = s.expect_re(_litstr_ml_re).group(0) + s.expect('\'\'\'') + else: + r = s.expect_re(_litstr_re).group(0) + s.expect('\'') + return r + return s.expect_re(_key_re).group(0) + +_float_re = re.compile(r'[+-]?(?:0|[1-9](?:_?\d)*)(?:\.\d(?:_?\d)*)?(?:[eE][+-]?(?:\d(?:_?\d)*))?') + +_basicstr_ml_re = re.compile(r'(?:""?(?!")|[^"\\\000-\011\013-\037])*') +_litstr_re = re.compile(r"[^'\000\010\012-\037]*") +_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\010\013-\037]))*") +def _p_value(s, object_pairs_hook): + pos = s.pos() + + if s.consume('true'): + return 'bool', s.last(), True, pos + if s.consume('false'): + return 'bool', s.last(), False, pos + + if s.consume('"'): + if s.consume('""'): + r = _p_basicstr_content(s, _basicstr_ml_re) + s.expect('"""') + else: + r = _p_basicstr_content(s, _basicstr_re) + s.expect('"') + return 'str', r, r, pos + + if s.consume('\''): + if s.consume('\'\''): + r = s.expect_re(_litstr_ml_re).group(0) + s.expect('\'\'\'') + else: + r = s.expect_re(_litstr_re).group(0) + s.expect('\'') + return 'str', r, r, pos + + if s.consume_re(rfc3339_re): + m = s.last() + return 'datetime', m.group(0), parse_rfc3339_re(m), pos + + if s.consume_re(_float_re): + m = s.last().group(0) + r = m.replace('_','') + if '.' in m or 'e' in m or 'E' in m: + return 'float', m, float(r), pos + else: + return 'int', m, int(r, 10), pos + + if s.consume('['): + items = [] + with s: + while True: + _p_ews(s) + items.append(_p_value(s, object_pairs_hook=object_pairs_hook)) + s.commit() + _p_ews(s) + s.expect(',') + s.commit() + _p_ews(s) + s.expect(']') + return 'array', None, items, pos + + if s.consume('{'): + _p_ws(s) + items = object_pairs_hook() + if not s.consume('}'): + k = _p_key(s) + _p_ws(s) + s.expect('=') + _p_ws(s) + items[k] = _p_value(s, object_pairs_hook=object_pairs_hook) + _p_ws(s) + while s.consume(','): + _p_ws(s) + k = _p_key(s) + _p_ws(s) + s.expect('=') + _p_ws(s) + items[k] = _p_value(s, object_pairs_hook=object_pairs_hook) + _p_ws(s) + s.expect('}') + return 'table', None, items, pos + + s.fail() + +def _p_stmt(s, object_pairs_hook): + pos = s.pos() + if s.consume( '['): + is_array = s.consume('[') + _p_ws(s) + keys = [_p_key(s)] + _p_ws(s) + while s.consume('.'): + _p_ws(s) + keys.append(_p_key(s)) + _p_ws(s) + s.expect(']') + if is_array: + s.expect(']') + return 'table_array' if is_array else 'table', keys, pos + + key = _p_key(s) + _p_ws(s) + s.expect('=') + _p_ws(s) + value = _p_value(s, object_pairs_hook=object_pairs_hook) + return 'kv', (key, value), pos + +_stmtsep_re = re.compile(r'(?:[ \t]*(?:#[^\n]*)?\n)+[ \t]*') +def _p_toml(s, object_pairs_hook): + stmts = [] + _p_ews(s) + with s: + stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook)) + while True: + s.commit() + s.expect_re(_stmtsep_re) + stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook)) + _p_ews(s) + s.expect_eof() + return stmts diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/test.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/test.py new file mode 100644 index 0000000..ec8abfc --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/test.py @@ -0,0 +1,30 @@ +import datetime +from .utils import format_rfc3339 + +try: + _string_types = (str, unicode) + _int_types = (int, long) +except NameError: + _string_types = str + _int_types = int + +def translate_to_test(v): + if isinstance(v, dict): + return { k: translate_to_test(v) for k, v in v.items() } + if isinstance(v, list): + a = [translate_to_test(x) for x in v] + if v and isinstance(v[0], dict): + return a + else: + return {'type': 'array', 'value': a} + if isinstance(v, datetime.datetime): + return {'type': 'datetime', 'value': format_rfc3339(v)} + if isinstance(v, bool): + return {'type': 'bool', 'value': 'true' if v else 'false'} + if isinstance(v, _int_types): + return {'type': 'integer', 'value': str(v)} + if isinstance(v, float): + return {'type': 'float', 'value': '{:.17}'.format(v)} + if isinstance(v, _string_types): + return {'type': 'string', 'value': v} + raise RuntimeError('unexpected value: {!r}'.format(v)) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/utils.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/utils.py new file mode 100644 index 0000000..636a680 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/utils.py @@ -0,0 +1,67 @@ +import datetime +import re + +rfc3339_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|([+-]\d{2}):(\d{2}))') + +def parse_rfc3339(v): + m = rfc3339_re.match(v) + if not m or m.group(0) != v: + return None + return parse_rfc3339_re(m) + +def parse_rfc3339_re(m): + r = map(int, m.groups()[:6]) + if m.group(7): + micro = float(m.group(7)) + else: + micro = 0 + + if m.group(8): + g = int(m.group(8), 10) * 60 + int(m.group(9), 10) + tz = _TimeZone(datetime.timedelta(0, g * 60)) + else: + tz = _TimeZone(datetime.timedelta(0, 0)) + + y, m, d, H, M, S = r + return datetime.datetime(y, m, d, H, M, S, int(micro * 1000000), tz) + + +def format_rfc3339(v): + offs = v.utcoffset() + offs = int(offs.total_seconds()) // 60 if offs is not None else 0 + + if offs == 0: + suffix = 'Z' + else: + if offs > 0: + suffix = '+' + else: + suffix = '-' + offs = -offs + suffix = '{0}{1:02}:{2:02}'.format(suffix, offs // 60, offs % 60) + + if v.microsecond: + return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix + else: + return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix + +class _TimeZone(datetime.tzinfo): + def __init__(self, offset): + self._offset = offset + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return None + + def tzname(self, dt): + m = self._offset.total_seconds() // 60 + if m < 0: + res = '-' + m = -m + else: + res = '+' + h = m // 60 + m = m - h * 60 + return '{}{:.02}{:.02}'.format(res, h, m) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/writer.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/writer.py new file mode 100644 index 0000000..73b5089 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/pytoml/writer.py @@ -0,0 +1,106 @@ +from __future__ import unicode_literals +import io, datetime, math, string, sys + +from .utils import format_rfc3339 + +if sys.version_info[0] == 3: + long = int + unicode = str + + +def dumps(obj, sort_keys=False): + fout = io.StringIO() + dump(obj, fout, sort_keys=sort_keys) + return fout.getvalue() + + +_escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'} + + +def _escape_string(s): + res = [] + start = 0 + + def flush(): + if start != i: + res.append(s[start:i]) + return i + 1 + + i = 0 + while i < len(s): + c = s[i] + if c in '"\\\n\r\t\b\f': + start = flush() + res.append('\\' + _escapes[c]) + elif ord(c) < 0x20: + start = flush() + res.append('\\u%04x' % ord(c)) + i += 1 + + flush() + return '"' + ''.join(res) + '"' + + +_key_chars = string.digits + string.ascii_letters + '-_' +def _escape_id(s): + if any(c not in _key_chars for c in s): + return _escape_string(s) + return s + + +def _format_value(v): + if isinstance(v, bool): + return 'true' if v else 'false' + if isinstance(v, int) or isinstance(v, long): + return unicode(v) + if isinstance(v, float): + if math.isnan(v) or math.isinf(v): + raise ValueError("{0} is not a valid TOML value".format(v)) + else: + return repr(v) + elif isinstance(v, unicode) or isinstance(v, bytes): + return _escape_string(v) + elif isinstance(v, datetime.datetime): + return format_rfc3339(v) + elif isinstance(v, list): + return '[{0}]'.format(', '.join(_format_value(obj) for obj in v)) + elif isinstance(v, dict): + return '{{{0}}}'.format(', '.join('{} = {}'.format(_escape_id(k), _format_value(obj)) for k, obj in v.items())) + else: + raise RuntimeError(v) + + +def dump(obj, fout, sort_keys=False): + tables = [((), obj, False)] + + while tables: + name, table, is_array = tables.pop() + if name: + section_name = '.'.join(_escape_id(c) for c in name) + if is_array: + fout.write('[[{0}]]\n'.format(section_name)) + else: + fout.write('[{0}]\n'.format(section_name)) + + table_keys = sorted(table.keys()) if sort_keys else table.keys() + new_tables = [] + has_kv = False + for k in table_keys: + v = table[k] + if isinstance(v, dict): + new_tables.append((name + (k,), v, False)) + elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v): + new_tables.extend((name + (k,), d, True) for d in v) + elif v is None: + # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344 + fout.write( + '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k))) + has_kv = True + else: + fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v))) + has_kv = True + + tables.extend(reversed(new_tables)) + + if (name or has_kv) and tables: + fout.write('\n') diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__init__.py new file mode 100644 index 0000000..80c4ce1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__init__.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. Basic GET +usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> 'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key2": "value2", + "key1": "value1" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at <http://python-requests.org>. + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +from pip._vendor import urllib3 +from pip._vendor import chardet +import warnings +from .exceptions import RequestsDependencyWarning + + +def check_compatibility(urllib3_version, chardet_version): + 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') + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, <= 1.24 + assert major == 1 + assert minor >= 21 + assert minor <= 24 + + # Check chardet for compatibility. + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet >= 3.0.2, < 3.1.0 + assert major == 3 + assert minor < 1 + assert patch >= 2 + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + 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) + warnings.warn(warning, RequestsDependencyWarning) + +# Check imported dependencies for compatibility. +try: + check_compatibility(urllib3.__version__, chardet.__version__) +except (AssertionError, ValueError): + warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet.__version__), + RequestsDependencyWarning) + +# Attempt to enable urllib3's SNI support, if possible +from pip._internal.utils.compat import WINDOWS +if not WINDOWS: + try: + 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 +) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__version__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__version__.py new file mode 100644 index 0000000..f5b5d03 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = 'requests' +__description__ = 'Python HTTP for Humans.' +__url__ = 'http://python-requests.org' +__version__ = '2.21.0' +__build__ = 0x022100 +__author__ = 'Kenneth Reitz' +__author_email__ = 'me@kennethreitz.org' +__license__ = 'Apache 2.0' +__copyright__ = 'Copyright 2018 Kenneth Reitz' +__cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/_internal_utils.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/_internal_utils.py new file mode 100644 index 0000000..759d9a5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/_internal_utils.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +from .compat import is_py2, builtin_str, str + + +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. + """ + if isinstance(string, builtin_str): + out = string + else: + if is_py2: + out = string.encode(encoding) + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode('ascii') + return True + except UnicodeEncodeError: + return False diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/adapters.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/adapters.py new file mode 100644 index 0000000..c30e7c9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/adapters.py @@ -0,0 +1,533 @@ +# -*- coding: utf-8 -*- + +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket + +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.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 .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 + +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): + """The Base Transport Adapter""" + + def __init__(self): + super(BaseAdapter, self).__init__() + + 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. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session <Session>` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> 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): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block) + + 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 + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + 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. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith('socks'): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith('https') and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + 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)) + + 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.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + 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)) + 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)) + + def build_response(self, req, resp): + """Builds a :class:`Response <requests.Response>` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>` + + :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, 'status', None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode('utf-8') + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + 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.") + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + 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') + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers['Proxy-Authorization'] = _basic_auth_str(username, + password) + + return headers + + 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. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + 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) + + 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) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout + ) + + # Send the request. + else: + 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) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + 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(i) + 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() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False + ) + except: + # If we hit any problems here, clean up the connection. + # Then, reraise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/api.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/api.py new file mode 100644 index 0000000..abada96 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/api.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request <Request>`. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the body of the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response <Response>` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + <Response [200]> + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('get', url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('options', url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return request('head', url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('post', url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('put', url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('patch', url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('delete', url, **kwargs) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/auth.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/auth.py new file mode 100644 index 0000000..bdde51c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/auth.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- + +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import os +import re +import time +import hashlib +import threading +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 .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(password), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode('latin1') + + if isinstance(password, str): + password = password.encode('latin1') + + authstr = 'Basic ' + to_native_string( + b64encode(b':'.join((username, password))).strip() + ) + + return authstr + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + 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) + return r + + +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) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, 'init'): + self._thread_local.init = True + self._thread_local.last_nonce = '' + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :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') + hash_utf8 = None + + if algorithm is None: + _algorithm = 'MD5' + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': + def md5_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + hash_utf8 = md5_utf8 + elif _algorithm == 'SHA': + def sha_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + hash_utf8 = sha_utf8 + elif _algorithm == 'SHA-256': + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha256(x).hexdigest() + hash_utf8 = sha256_utf8 + elif _algorithm == 'SHA-512': + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha512(x).hexdigest() + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: 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 + + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (method, path) + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + 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') + s += os.urandom(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) + if _algorithm == 'MD5-SESS': + HA1 = hash_utf8('%s:%s:%s' % (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, noncebit) + else: + # XXX handle auth-int. + return None + + 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) + if opaque: + base += ', opaque="%s"' % opaque + if algorithm: + base += ', algorithm="%s"' % algorithm + if entdig: + base += ', digest="%s"' % entdig + if qop: + base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) + + return 'Digest %s' % (base) + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/requests/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # 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', '') + + 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)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + 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) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + 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) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # 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) + 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) + ]) + + def __ne__(self, other): + return not self == other diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/certs.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/certs.py new file mode 100644 index 0000000..06a594e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +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__': + print(where()) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/compat.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/compat.py new file mode 100644 index 0000000..6a86893 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/compat.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module handles import compatibility issues between Python 2 and +Python 3. +""" + +from pip._vendor import chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +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 + +# --------- +# 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 + 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) + +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 + from collections import OrderedDict + from collections.abc import Callable, Mapping, MutableMapping + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) + integer_types = (int,) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/cookies.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/cookies.py new file mode 100644 index 0000000..56fccd9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/cookies.py @@ -0,0 +1,549 @@ +# -*- coding: utf-8 -*- + +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import copy +import time +import calendar + +from ._internal_utils import to_native_string +from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest(object): + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + 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'): + 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') + 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 + ]) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + 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()") + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse(object): + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, '_original_response') and + response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get('Cookie') + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # 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')) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + 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) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + 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) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super(RequestsCookieJar, self).update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + 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: + return toReturn + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + 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__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + 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, + } + + badargs = set(kwargs) - set(result) + if badargs: + err = 'create_cookie() got unexpected keyword arguments: %s' + raise TypeError(err % 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']) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel['max-age']: + try: + 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) + ) + return create_cookie( + comment=morsel['comment'], + comment_url=bool(morsel['comment']), + discard=False, + domain=morsel['domain'], + expires=expires, + name=morsel.key, + path=morsel['path'], + port=None, + rest={'HttpOnly': morsel['httponly']}, + rfc2109=False, + secure=bool(morsel['secure']), + value=morsel.value, + version=morsel['version'] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError('You can only merge into CookieJar') + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict( + cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/exceptions.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/exceptions.py new file mode 100644 index 0000000..a91e1fd --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/exceptions.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from pip._vendor.urllib3.exceptions import HTTPError as BaseHTTPError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + 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 = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL schema (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """See defaults.py for valid schemas.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body""" + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + pass + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + pass + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" + pass diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/help.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/help.py new file mode 100644 index 0000000..3c3072b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/help.py @@ -0,0 +1,119 @@ +"""Module containing bug report helper(s).""" +from __future__ import print_function + +import json +import platform +import sys +import ssl + +from pip._vendor import idna +from pip._vendor import urllib3 +from pip._vendor import chardet + +from . import __version__ as requests_version + +try: + from pip._vendor.urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import OpenSSL + import cryptography + + +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'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + 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': + implementation_version = platform.python_version() # Complete Guess + elif implementation == 'IronPython': + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = 'Unknown' + + return {'name': implementation, 'version': implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + 'system': platform.system(), + 'release': platform.release(), + } + except IOError: + platform_info = { + 'system': 'Unknown', + 'release': 'Unknown', + } + + implementation_info = _implementation() + urllib3_info = {'version': urllib3.__version__} + chardet_info = {'version': chardet.__version__} + + pyopenssl_info = { + 'version': None, + 'openssl_version': '', + } + if OpenSSL: + pyopenssl_info = { + 'version': OpenSSL.__version__, + 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, + } + cryptography_info = { + 'version': getattr(cryptography, '__version__', ''), + } + idna_info = { + 'version': getattr(idna, '__version__', ''), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = { + 'version': '%x' % system_ssl 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, + 'pyOpenSSL': pyopenssl_info, + 'urllib3': urllib3_info, + 'chardet': chardet_info, + 'cryptography': cryptography_info, + 'idna': idna_info, + 'requests': { + 'version': requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/hooks.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/hooks.py new file mode 100644 index 0000000..7a51f21 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/hooks.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ['response'] + + +def default_hooks(): + return {event: [] for event in HOOKS} + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, '__call__'): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/models.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/models.py new file mode 100644 index 0000000..0839957 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/models.py @@ -0,0 +1,953 @@ +# -*- coding: utf-8 -*- + +""" +requests.models +~~~~~~~~~~~~~~~ + +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/requests/requests/issues/3578. +import encodings.idna + +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) +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 .compat import ( + Callable, Mapping, + cookielib, urlunparse, urlsplit, urlencode, str, bytes, + is_py2, chardet, builtin_str, basestring) +from .compat import json as complexjson +from .status_codes import codes + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin(object): + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = '/' + + url.append(path) + + query = p.query + if query: + url.append('?') + url.append(query) + + return ''.join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in to_key_val_list(data): + 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)) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if (not files): + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, '__iter__'): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + 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)) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, 'read'): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin(object): + 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)) + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, '__iter__'): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request <Request>` object. + + Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + <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): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return '<Request [%s]>' % (self.method) + + def prepare(self): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, + containing the exact bytes that will be sent to the server. + + Generated from either a :class:`Request <Request>` object or manually. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + <PreparedRequest [GET]> + + >>> s = requests.Session() + >>> s.send(r) + <Response [200]> + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: 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): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return '<PreparedRequest [%s]>' % (self.method) + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + from pip._vendor import idna + + try: + host = idna.encode(host, uts46=True).decode('utf-8') + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/requests/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode('utf8') + else: + url = unicode(url) if is_py2 else str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # 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'): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + 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) + + if not host: + raise InvalidURL("Invalid URL %r: No host supplied" % url) + + # 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 + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + 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.') + + # Carefully reconstruct the network location + netloc = auth or '' + if netloc: + netloc += '@' + netloc += host + if port: + netloc += ':' + str(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') + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = '%s&%s' % (query, enc_params) + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + 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' + body = complexjson.dumps(json) + if not isinstance(body, bytes): + body = body.encode('utf-8') + + is_stream = all([ + hasattr(data, '__iter__'), + not isinstance(data, (basestring, list, tuple, Mapping)) + ]) + + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + if is_stream: + body = data + + 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): + # 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.') + + if length: + self.headers['Content-Length'] = builtin_str(length) + else: + self.headers['Transfer-Encoding'] = 'chunked' + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + 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 + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + 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: + # 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' + + def prepare_auth(self, auth, url=''): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response(object): + """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' + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + # This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response <Response>` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest <PreparedRequest>` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) + + def __repr__(self): + return '<Response [%s]>' % (self.status_code) + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """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) + + @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)) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the chardet library.""" + 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 + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, 'stream'): + try: + for chunk in self.raw.stream(chunk_size, decode_content=True): + yield chunk + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + 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)) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + 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. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + for line in lines: + yield line + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_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_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return str('') + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + 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. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors='replace') + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises ValueError: If the response body does not contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using chardet to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + 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) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get('link') + + # l = MultiDict() + l = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get('rel') or link.get('url') + l[key] = link + + return l + + def raise_for_status(self): + """Raises stored :class:`HTTPError`, if one occurred.""" + + 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') + except UnicodeDecodeError: + 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) + + elif 500 <= self.status_code < 600: + http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, 'release_conn', None) + if release_conn is not None: + release_conn() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/packages.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/packages.py new file mode 100644 index 0000000..9582fa7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/packages.py @@ -0,0 +1,16 @@ +import sys + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna', 'chardet'): + vendored_package = "pip._vendor." + package + locals()[package] = __import__(vendored_package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == vendored_package or mod.startswith(vendored_package + '.'): + unprefixed_mod = mod[len("pip._vendor."):] + sys.modules['pip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] + +# Kinda cool, though, right? diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/sessions.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/sessions.py new file mode 100644 index 0000000..d73d700 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/sessions.py @@ -0,0 +1,770 @@ +# -*- coding: utf-8 -*- + +""" +requests.session +~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from datetime import timedelta + +from .auth import _basic_auth_str +from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping +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 +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 +) + +from .status_codes import codes + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI + +# 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 +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and + isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get('response') == []: + return request_hooks + + 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): + + 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 + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + 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') + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # 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)): + 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): + 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): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith('//'): + parsed_rurl = urlparse(resp.url) + url = '%s:%s' % (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: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/requests/requests/issues/1084 + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): + # https://github.com/requests/requests/issues/3490 + 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 + try: + del headers['Cookie'] + except KeyError: + pass + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # 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) + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.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'] + + # .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) + + return + + 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 + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :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') + + 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'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + 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' + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + 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' + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + <Response [200]> + + Or as a context manager:: + + >>> with requests.Session() as s: + >>> s.get('https://httpbin.org/get') + <Response [200]> + """ + + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'max_redirects', + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request <Request>` sent from this + #: :class:`Session <Session>`. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request <Request>`. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request <Request>`. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request <Request>`. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', HTTPAdapter()) + self.mount('http://', HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request <Request>` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + 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, + hooks=merge_hooks(request.hooks, self.hooks), + ) + 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): + """Constructs a :class:`Request <Request>`, prepares it and sends it. + Returns :class:`Response <Response>` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + 'timeout': timeout, + 'allow_redirects': allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PUT', url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PATCH', url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('DELETE', url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # 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.proxies) + + # 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.') + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + + # Resolve redirects if allowed. + history = [resp for resp in gen] if allow_redirects else [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # 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)) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # 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 + 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. + if verify is True or verify is None: + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema("No connection adapters were found for '%s'" % url) + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/status_codes.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/status_codes.py new file mode 100644 index 0000000..813e8c4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/status_codes.py @@ -0,0 +1,120 @@ +# -*- 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 +items. + +>>> requests.codes['temporary_redirect'] +307 +>>> requests.codes.teapot +418 +>>> requests.codes['\o/'] +200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +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',), + + # 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 + + # 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',), + + # 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'), +} + +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(('\\', '/')): + setattr(codes, title.upper(), code) + + def doc(code): + names = ', '.join('``%s``' % 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) + +_init() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/structures.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/structures.py new file mode 100644 index 0000000..da930e2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/structures.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from .compat import OrderedDict, Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super(LookupDict, self).__init__() + + def __repr__(self): + return '<lookup \'%s\'>' % (self.name) + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/utils.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/utils.py new file mode 100644 index 0000000..8170a8d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/requests/utils.py @@ -0,0 +1,977 @@ +# -*- coding: utf-8 -*- + +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile + +from .__version__ import __version__ +from . import certs +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import to_native_string +from .compat import parse_http_list as _parse_list_header +from .compat import ( + quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, + proxy_bypass, urlunparse, basestring, integer_types, is_py3, + proxy_bypass_environment, getproxies_environment, Mapping) +from .cookies import cookiejar_from_dict +from .structures import CaseInsensitiveDict +from .exceptions import ( + InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) + +NETRC_FILES = ('.netrc', '_netrc') + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {'http': 80, 'https': 443} + + +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 + except ImportError: + return False + + try: + 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]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, + 'ProxyOverride')[0] + except OSError: + return False + if not proxyEnable or not proxyOverride: + return False + + # 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(';') + # now check if we match one of the registry values. + for test in proxyOverride: + 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 + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, '__len__'): + total_length = len(o) + + elif hasattr(o, 'len'): + total_length = o.len + + elif hasattr(o, 'fileno'): + try: + fileno = o.fileno() + except io.UnsupportedOperation: + 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 hasattr(o, 'tell'): + try: + current_position = o.tell() + except (OSError, IOError): + # 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 + # let requests chunk it instead. + 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 + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except (OSError, IOError): + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + try: + from netrc import netrc, NetrcParseError + + netrc_path = None + + for f in NETRC_FILES: + try: + loc = os.path.expanduser('~/{}'.format(f)) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/requests/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b':' + if isinstance(url, str): + 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) + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, IOError): + # 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: + raise + + # AppEngine hackiness. + except (ImportError, AttributeError): + pass + + +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] != '>'): + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + 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 zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return 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('/')) + if not os.path.exists(extracted_path): + extracted_path = zip_file.extract(member, path=tmp) + + return extracted_path + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples. + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if '=' not in item: + result[item] = None + continue + name, value = item.split('=', 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # 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('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :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) + + 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)) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(';') + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + 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) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + 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 'text' in content_type: + return 'ISO-8859-1' + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes a iterator.""" + + if r.encoding is None: + for item in iterator: + yield item + return + + 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) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos:pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :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) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors='replace') + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + 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) + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = '%' + parts[i] + else: + parts[i] = '%' + parts[i] + return ''.join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :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 + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xffffffff ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack('>I', bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except socket.error: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count('/') == 1: + try: + mask = int(string_network.split('/')[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split('/')[0]) + except socket.error: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # 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()) + + # 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') + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + 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 + ) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += ':{}'.format(parsed.port) + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + 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) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get('all')) + + proxy_keys = [ + urlparts.scheme + '://' + urlparts.hostname, + urlparts.scheme, + 'all://' + urlparts.hostname, + 'all', + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return '%s/%s' % (name, __version__) + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict({ + 'User-Agent': default_user_agent(), + 'Accept-Encoding': ', '.join(('gzip', 'deflate')), + 'Accept': '*/*', + 'Connection': 'keep-alive', + }) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = ' \'"' + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(', *<', value): + try: + url, params = val.split(';', 1) + except ValueError: + url, params = val, '' + + link = {'url': url.strip('<> \'"')} + + for param in params.split(';'): + try: + key, value = param.split('=') + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # 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 + if sample[:3] == codecs.BOM_UTF8: + 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 + nullcount = sample.count(_null) + if nullcount == 0: + return 'utf-8' + if nullcount == 2: + 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' + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return 'utf-32-be' + if sample[1:] == _null3: + return 'utf-32-le' + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :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. + if not netloc: + netloc, path = path, netloc + + return urlunparse((scheme, netloc, path, params, query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + 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. + + :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))) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + 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): + try: + body_seek(prepared_request._body_position) + except (IOError, 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/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/retrying.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/retrying.py new file mode 100644 index 0000000..6d1e627 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/retrying.py @@ -0,0 +1,267 @@ +## Copyright 2013-2014 Ray Holder +## +## 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 random +from pip._vendor import six +import sys +import time +import traceback + + +# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint... +MAX_WAIT = 1073741823 + + +def retry(*dargs, **dkw): + """ + Decorator function that instantiates the Retrying object + @param *dargs: positional arguments passed to Retrying object + @param **dkw: keyword arguments passed to the Retrying object + """ + # support both @retry and @retry() as valid syntax + if len(dargs) == 1 and callable(dargs[0]): + def wrap_simple(f): + + @six.wraps(f) + def wrapped_f(*args, **kw): + return Retrying().call(f, *args, **kw) + + return wrapped_f + + return wrap_simple(dargs[0]) + + else: + def wrap(f): + + @six.wraps(f) + def wrapped_f(*args, **kw): + return Retrying(*dargs, **dkw).call(f, *args, **kw) + + return wrapped_f + + return wrap + + +class Retrying(object): + + def __init__(self, + stop=None, wait=None, + stop_max_attempt_number=None, + stop_max_delay=None, + wait_fixed=None, + wait_random_min=None, wait_random_max=None, + wait_incrementing_start=None, wait_incrementing_increment=None, + wait_exponential_multiplier=None, wait_exponential_max=None, + retry_on_exception=None, + retry_on_result=None, + wrap_exception=False, + stop_func=None, + wait_func=None, + wait_jitter_max=None): + + self._stop_max_attempt_number = 5 if stop_max_attempt_number is None else stop_max_attempt_number + self._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay + self._wait_fixed = 1000 if wait_fixed is None else wait_fixed + self._wait_random_min = 0 if wait_random_min is None else wait_random_min + self._wait_random_max = 1000 if wait_random_max is None else wait_random_max + self._wait_incrementing_start = 0 if wait_incrementing_start is None else wait_incrementing_start + self._wait_incrementing_increment = 100 if wait_incrementing_increment is None else wait_incrementing_increment + self._wait_exponential_multiplier = 1 if wait_exponential_multiplier is None else wait_exponential_multiplier + self._wait_exponential_max = MAX_WAIT if wait_exponential_max is None else wait_exponential_max + self._wait_jitter_max = 0 if wait_jitter_max is None else wait_jitter_max + + # TODO add chaining of stop behaviors + # stop behavior + stop_funcs = [] + if stop_max_attempt_number is not None: + stop_funcs.append(self.stop_after_attempt) + + if stop_max_delay is not None: + stop_funcs.append(self.stop_after_delay) + + if stop_func is not None: + self.stop = stop_func + + elif stop is None: + self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs) + + else: + self.stop = getattr(self, stop) + + # TODO add chaining of wait behaviors + # wait behavior + wait_funcs = [lambda *args, **kwargs: 0] + if wait_fixed is not None: + wait_funcs.append(self.fixed_sleep) + + if wait_random_min is not None or wait_random_max is not None: + wait_funcs.append(self.random_sleep) + + if wait_incrementing_start is not None or wait_incrementing_increment is not None: + wait_funcs.append(self.incrementing_sleep) + + if wait_exponential_multiplier is not None or wait_exponential_max is not None: + wait_funcs.append(self.exponential_sleep) + + if wait_func is not None: + self.wait = wait_func + + elif wait is None: + self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs) + + else: + self.wait = getattr(self, wait) + + # retry on exception filter + if retry_on_exception is None: + self._retry_on_exception = self.always_reject + else: + self._retry_on_exception = retry_on_exception + + # TODO simplify retrying by Exception types + # retry on result filter + if retry_on_result is None: + self._retry_on_result = self.never_reject + else: + self._retry_on_result = retry_on_result + + self._wrap_exception = wrap_exception + + def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms): + """Stop after the previous attempt >= stop_max_attempt_number.""" + return previous_attempt_number >= self._stop_max_attempt_number + + def stop_after_delay(self, previous_attempt_number, delay_since_first_attempt_ms): + """Stop after the time from the first attempt >= stop_max_delay.""" + return delay_since_first_attempt_ms >= self._stop_max_delay + + def no_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): + """Don't sleep at all before retrying.""" + return 0 + + def fixed_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): + """Sleep a fixed amount of time between each retry.""" + return self._wait_fixed + + def random_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): + """Sleep a random amount of time between wait_random_min and wait_random_max""" + return random.randint(self._wait_random_min, self._wait_random_max) + + def incrementing_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): + """ + Sleep an incremental amount of time after each attempt, starting at + wait_incrementing_start and incrementing by wait_incrementing_increment + """ + result = self._wait_incrementing_start + (self._wait_incrementing_increment * (previous_attempt_number - 1)) + if result < 0: + result = 0 + return result + + def exponential_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): + exp = 2 ** previous_attempt_number + result = self._wait_exponential_multiplier * exp + if result > self._wait_exponential_max: + result = self._wait_exponential_max + if result < 0: + result = 0 + return result + + def never_reject(self, result): + return False + + def always_reject(self, result): + return True + + def should_reject(self, attempt): + reject = False + if attempt.has_exception: + reject |= self._retry_on_exception(attempt.value[1]) + else: + reject |= self._retry_on_result(attempt.value) + + return reject + + def call(self, fn, *args, **kwargs): + start_time = int(round(time.time() * 1000)) + attempt_number = 1 + while True: + try: + attempt = Attempt(fn(*args, **kwargs), attempt_number, False) + except: + tb = sys.exc_info() + attempt = Attempt(tb, attempt_number, True) + + if not self.should_reject(attempt): + return attempt.get(self._wrap_exception) + + delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time + if self.stop(attempt_number, delay_since_first_attempt_ms): + if not self._wrap_exception and attempt.has_exception: + # get() on an attempt with an exception should cause it to be raised, but raise just in case + raise attempt.get() + else: + raise RetryError(attempt) + else: + sleep = self.wait(attempt_number, delay_since_first_attempt_ms) + if self._wait_jitter_max: + jitter = random.random() * self._wait_jitter_max + sleep = sleep + max(0, jitter) + time.sleep(sleep / 1000.0) + + attempt_number += 1 + + +class Attempt(object): + """ + An Attempt encapsulates a call to a target function that may end as a + normal return value from the function or an Exception depending on what + occurred during the execution. + """ + + def __init__(self, value, attempt_number, has_exception): + self.value = value + self.attempt_number = attempt_number + self.has_exception = has_exception + + def get(self, wrap_exception=False): + """ + Return the return value of this Attempt instance or raise an Exception. + If wrap_exception is true, this Attempt is wrapped inside of a + RetryError before being raised. + """ + if self.has_exception: + if wrap_exception: + raise RetryError(self) + else: + six.reraise(self.value[0], self.value[1], self.value[2]) + else: + return self.value + + def __repr__(self): + if self.has_exception: + return "Attempts: {0}, Error:\n{1}".format(self.attempt_number, "".join(traceback.format_tb(self.value[2]))) + else: + return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value) + + +class RetryError(Exception): + """ + A RetryError encapsulates the last Attempt instance right before giving up. + """ + + def __init__(self, last_attempt): + self.last_attempt = last_attempt + + def __str__(self): + return "RetryError[{0}]".format(self.last_attempt) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/six.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/six.py new file mode 100644 index 0000000..89b2188 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/six.py @@ -0,0 +1,952 @@ +# Copyright (c) 2010-2018 Benjamin Peterson +# +# 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. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.12.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + try: + if from_value is None: + raise value + raise value from from_value + finally: + value = None +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + if hasattr(cls, '__qualname__'): + orig_vars['__qualname__'] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def ensure_binary(s, encoding='utf-8', errors='strict'): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, text_type): + return s.encode(encoding, errors) + elif isinstance(s, binary_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding='utf-8', errors='strict'): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + if PY2 and isinstance(s, text_type): + s = s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + s = s.decode(encoding, errors) + return s + + +def ensure_text(s, encoding='utf-8', errors='strict'): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/__init__.py new file mode 100644 index 0000000..148a9c3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/__init__.py @@ -0,0 +1,92 @@ +""" +urllib3 - Thread-safe connection pooling and re-using. +""" + +from __future__ import absolute_import +import warnings + +from .connectionpool import ( + HTTPConnectionPool, + HTTPSConnectionPool, + connection_from_url +) + +from . import exceptions +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.url import get_host +from .util.timeout import Timeout +from .util.retry import Retry + + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' +__license__ = 'MIT' +__version__ = '1.24.1' + +__all__ = ( + 'HTTPConnectionPool', + 'HTTPSConnectionPool', + 'PoolManager', + 'ProxyManager', + 'HTTPResponse', + 'Retry', + 'Timeout', + 'add_stderr_logger', + 'connection_from_url', + 'disable_warnings', + 'encode_multipart_formdata', + 'get_host', + 'make_headers', + 'proxy_from_url', +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug('Added a stderr logging handler to logger: %s', __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter('always', exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter('default', exceptions.InsecurePlatformWarning, + append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter('ignore', category) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/_collections.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/_collections.py new file mode 100644 index 0000000..34f2381 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/_collections.py @@ -0,0 +1,329 @@ +from __future__ import absolute_import +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +from collections import OrderedDict +from .exceptions import InvalidHeader +from .packages.six import iterkeys, itervalues, PY3 + + +__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = [key, val] + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ', '.join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, 'keys'): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return (dict((k.lower(), v) for k, v in self.itermerged()) == + dict((k.lower(), v) for k, v in other.itermerged())) + + def __ne__(self, other): + return not self.__eq__(other) + + if not PY3: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + ''' + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + vals.append(val) + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError("extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args))) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key, default=__marker): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is self.__marker: + return [] + return default + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ', '.join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (' ', '\t') + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + 'Header continuation with no previous header: %s' % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + ' ' + line.strip()) + continue + + key, value = line.split(':', 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connection.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connection.py new file mode 100644 index 0000000..02b3665 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connection.py @@ -0,0 +1,391 @@ +from __future__ import absolute_import +import datetime +import logging +import os +import socket +from socket import error as SocketError, timeout as SocketTimeout +import warnings +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 + +try: # Compiled with SSL? + import ssl + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: # Python 2: + class ConnectionError(Exception): + pass + + +from .exceptions import ( + NewConnectionError, + ConnectTimeoutError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .packages.ssl_match_hostname import match_hostname, CertificateError + +from .util.ssl_ import ( + resolve_cert_reqs, + resolve_ssl_version, + assert_fingerprint, + create_urllib3_context, + ssl_wrap_socket +) + + +from .util import connection + +from ._collections import HTTPHeaderDict + +log = logging.getLogger(__name__) + +port_by_scheme = { + 'http': 80, + 'https': 443, +} + +# When updating RECENT_DATE, move it to within two years of the current date, +# and not less than 6 months ago. +# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or +# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months) +RECENT_DATE = datetime.date(2017, 6, 30) + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + pass + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on httplib.HTTPConnection but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass:: + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme['http'] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + def __init__(self, *args, **kw): + if six.PY3: # Python 3 + kw.pop('strict', None) + + # Pre-set source_address. + self.source_address = kw.get('source_address') + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop('socket_options', self.default_socket_options) + + _HTTPConnection.__init__(self, *args, **kw) + + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip('.') + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self): + """ Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw['source_address'] = self.source_address + + if self.socket_options: + extra_kw['socket_options'] = self.socket_options + + try: + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e) + + return conn + + def _prepare_conn(self, conn): + self.sock = conn + if self._tunnel_host: + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = HTTPHeaderDict(headers if headers is not None else {}) + skip_accept_encoding = 'accept-encoding' in headers + skip_host = 'host' in headers + self.putrequest( + method, + url, + skip_accept_encoding=skip_accept_encoding, + skip_host=skip_host + ) + for header, value in headers.items(): + self.putheader(header, value) + if 'transfer-encoding' not in headers: + self.putheader('Transfer-Encoding', 'chunked') + self.endheaders() + + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: + if not chunk: + continue + if not isinstance(chunk, bytes): + chunk = chunk.encode('utf8') + len_str = hex(len(chunk))[2:] + self.send(len_str.encode('utf-8')) + self.send(b'\r\n') + self.send(chunk) + self.send(b'\r\n') + + # After the if clause, to always have a closed body + self.send(b'0\r\n\r\n') + + +class HTTPSConnection(HTTPConnection): + default_port = port_by_scheme['https'] + + ssl_version = None + + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, server_hostname=None, **kw): + + HTTPConnection.__init__(self, host, port, strict=strict, + timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + self.ssl_context = ssl_context + self.server_hostname = server_hostname + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = 'https' + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + if self.ssl_context is None: + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(None), + cert_reqs=resolve_cert_reqs(None), + ) + + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + ssl_context=self.ssl_context, + server_hostname=self.server_hostname + ) + + +class VerifiedHTTPSConnection(HTTPSConnection): + """ + Based on httplib.HTTPSConnection but wraps the socket with + SSL certification. + """ + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ssl_version = None + assert_fingerprint = None + + def set_cert(self, key_file=None, cert_file=None, + cert_reqs=None, ca_certs=None, + assert_hostname=None, assert_fingerprint=None, + ca_cert_dir=None): + """ + This method should only be called once, before the connection is used. + """ + # If cert_reqs is not provided, we can try to guess. If the user gave + # us a cert database, we assume they want to use it: otherwise, if + # they gave us an SSL Context object we should use whatever is set for + # it. + if cert_reqs is None: + if ca_certs or ca_cert_dir: + cert_reqs = 'CERT_REQUIRED' + elif self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + + def connect(self): + # Add certificate verification + conn = self._new_conn() + hostname = self.host + + if self._tunnel_host: + self.sock = conn + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn(( + 'System time is way off (before {0}). This will probably ' + 'lead to SSL verification errors').format(RECENT_DATE), + SystemTimeWarning + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + if self.ssl_context is None: + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) + + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + server_hostname=server_hostname, + ssl_context=context) + + if self.assert_fingerprint: + assert_fingerprint(self.sock.getpeercert(binary_form=True), + self.assert_fingerprint) + elif context.verify_mode != ssl.CERT_NONE \ + and not getattr(context, 'check_hostname', False) \ + and self.assert_hostname is not False: + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get('subjectAltName', ()): + warnings.warn(( + 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' + '`commonName` for now. This feature is being removed by major browsers and ' + 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' + 'for details.)'.format(hostname)), + SubjectAltNameWarning + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED or + self.assert_fingerprint is not None + ) + + +def _match_hostname(cert, asserted_hostname): + try: + match_hostname(cert, asserted_hostname) + except CertificateError as e: + log.error( + 'Certificate did not match expected hostname: %s. ' + 'Certificate: %s', asserted_hostname, cert + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert + raise + + +if ssl: + # Make a copy for testing. + UnverifiedHTTPSConnection = HTTPSConnection + HTTPSConnection = VerifiedHTTPSConnection +else: + HTTPSConnection = DummyConnection diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connectionpool.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connectionpool.py new file mode 100644 index 0000000..f7a8f19 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/connectionpool.py @@ -0,0 +1,896 @@ +from __future__ import absolute_import +import errno +import logging +import sys +import warnings + +from socket import error as SocketError, timeout as SocketTimeout +import socket + + +from .exceptions import ( + ClosedPoolError, + ProtocolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + LocationValueError, + MaxRetryError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, + InsecureRequestWarning, + NewConnectionError, +) +from .packages.ssl_match_hostname import CertificateError +from .packages import six +from .packages.six.moves import queue +from .connection import ( + port_by_scheme, + DummyConnection, + HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, + HTTPException, BaseSSLError, +) +from .request import RequestMethods +from .response import HTTPResponse + +from .util.connection import is_connection_dropped +from .util.request import set_file_position +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host, Url, NORMALIZABLE_SCHEMES +from .util.queue import LifoQueue + + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = _ipv6_host(host, self.scheme) + self._proxy_host = host.lower() + self.port = port + + def __str__(self): + return '%s(host=%r, port=%r)' % (type(self).__name__, + self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`httplib.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`httplib.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`httplib.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.connectionpool.ProxyManager`" + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.connectionpool.ProxyManager`" + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = 'http' + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse + + def __init__(self, host, port=None, strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, + headers=None, retries=None, + _proxy=None, _proxy_headers=None, + **conn_kw): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault('socket_options', []) + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug("Starting new HTTP connection (%d): %s:%s", + self.num_connections, self.host, self.port or "80") + + conn = self.ConnectionCls(host=self.host, port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, **self.conn_kw) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except queue.Empty: + if self.block: + raise EmptyPoolError(self, + "Pool reached maximum size and no more " + "connections are allowed.") + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + if getattr(conn, 'auto_open', 1) == 0: + # This is a proxied connection that has been mutated by + # httplib._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s", + self.host) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """ Helper that always returns a :class:`urllib3.util.Timeout` """ + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, 'errno') and err.errno in _blocking_errnos: + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python < 2.7.4 + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + def _make_request(self, conn, method, url, timeout=_Default, chunked=False, + **httplib_request_kw): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls httplib.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, 'sock', None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: # Python 3 + try: + httplib_response = conn.getresponse() + except Exception as e: + # Remove the TypeError from the exception chain in Python 3; + # otherwise it looks like a programming error was the cause. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') + log.debug("%s://%s:%s \"%s %s %s\" %s %s", self.scheme, self.host, self.port, + method, url, http_version, httplib_response.status, + httplib_response.length) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + 'Failed to parse headers (url=%s): %s', + self._absolute_url(url), hpe, exc_info=True) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except queue.Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith('/'): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + + host = _ipv6_host(host, self.scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen(self, method, url, body=None, headers=None, retries=None, + redirect=True, assert_same_host=True, timeout=_Default, + pool_timeout=None, release_conn=None, chunked=False, + body_pos=None, **response_kw): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param body: + Data to send in the request body (useful for creating + POST requests, see HTTPConnectionPool.post_url for + more convenience). + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When False, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get('preload_content', True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] <https://github.com/shazow/urllib3/issues/651> + release_this_conn = release_conn + + # Merge the proxy headers. Only do this in HTTP. We have to copy the + # headers dict so we can safely change it without those changes being + # reflected in anyone else's copy. + if self.scheme == 'http': + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) + if is_new_proxy_conn: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request(conn, method, url, + timeout=timeout_obj, + body=body, headers=headers, + chunked=chunked) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw['request_method'] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib(httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw) + + # Everything went great! + clean_exit = True + + except queue.Empty: + # Timed out by queue. + raise EmptyPoolError(self, "No pool connections are available.") + + except (TimeoutError, HTTPException, SocketError, ProtocolError, + BaseSSLError, SSLError, CertificateError) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + if isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError('Cannot connect to proxy.', e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError('Connection aborted.', e) + + retries = retries.increment(method, url, error=e, _pool=self, + _stacktrace=sys.exc_info()[2]) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + conn = conn and conn.close() + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning("Retrying (%r) after connection " + "broken by '%r': %s", retries, err, url) + return self.urlopen(method, url, body, headers, retries, + redirect, assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, body_pos=body_pos, + **response_kw) + + def drain_and_release_conn(response): + try: + # discard any remaining response body, the connection will be + # released back to the pool once the entire response is read + response.read() + except (TimeoutError, HTTPException, SocketError, ProtocolError, + BaseSSLError, SSLError) as e: + pass + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = 'GET' + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + # Drain and release the connection for this response, since + # we're not returning it to be released manually. + drain_and_release_conn(response) + raise + return response + + # drain and return the connection to the pool before recursing + drain_and_release_conn(response) + + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, redirect_location, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, body_pos=body_pos, + **response_kw) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.getheader('Retry-After')) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + # Drain and release the connection for this response, since + # we're not returning it to be released manually. + drain_and_release_conn(response) + raise + return response + + # drain and return the connection to the pool before recursing + drain_and_release_conn(response) + + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, url, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, + body_pos=body_pos, **response_kw) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + When Python is compiled with the :mod:`ssl` module, then + :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, + instead of :class:`.HTTPSConnection`. + + :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is + available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = 'https' + ConnectionCls = HTTPSConnection + + def __init__(self, host, port=None, + strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, + block=False, headers=None, retries=None, + _proxy=None, _proxy_headers=None, + key_file=None, cert_file=None, cert_reqs=None, + ca_certs=None, ssl_version=None, + assert_hostname=None, assert_fingerprint=None, + ca_cert_dir=None, **conn_kw): + + HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, + block, headers, retries, _proxy, _proxy_headers, + **conn_kw) + + if ca_certs and cert_reqs is None: + cert_reqs = 'CERT_REQUIRED' + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert(key_file=self.key_file, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establish tunnel connection early, because otherwise httplib + would improperly set Host: header to proxy's IP:port. + """ + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`httplib.HTTPSConnection`. + """ + self.num_connections += 1 + log.debug("Starting new HTTPS connection (%d): %s:%s", + self.num_connections, self.host, self.port or "443") + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError("Can't connect to HTTPS URL because the SSL " + "module is not available.") + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls(host=actual_host, port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, **self.conn_kw) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn(( + 'Unverified HTTPS request is being made. ' + 'Adding certificate verification is strongly advised. See: ' + 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' + '#ssl-warnings'), + InsecureRequestWarning) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + port = port or port_by_scheme.get(scheme, 80) + if scheme == 'https': + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + + +def _ipv6_host(host, scheme): + """ + Process IPv6 address literals + """ + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + # + # Also if an IPv6 address literal has a zone identifier, the + # percent sign might be URIencoded, convert it back into ASCII + if host.startswith('[') and host.endswith(']'): + host = host.replace('%25', '%').strip('[]') + if scheme in NORMALIZABLE_SCHEMES: + host = host.lower() + return host diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_appengine_environ.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000..f3e0094 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,30 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return (is_local_appengine() or + is_prod_appengine() or + is_prod_appengine_mvms()) + + +def is_appengine_sandbox(): + return is_appengine() and not is_prod_appengine_mvms() + + +def is_local_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Development/' in os.environ['SERVER_SOFTWARE']) + + +def is_prod_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and + not is_prod_appengine_mvms()) + + +def is_prod_appengine_mvms(): + return os.environ.get('GAE_VM', False) == 'true' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/bindings.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000..bcf41c0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,593 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond <will@wbond.net> + + 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 absolute_import + +import platform +from ctypes.util import find_library +from ctypes import ( + c_void_p, c_int32, c_char_p, c_size_t, c_byte, c_uint32, c_ulong, c_long, + c_bool +) +from ctypes import CDLL, POINTER, CFUNCTYPE + + +security_path = find_library('Security') +if not security_path: + raise ImportError('The library Security could not be found') + + +core_foundation_path = find_library('CoreFoundation') +if not core_foundation_path: + raise ImportError('The library CoreFoundation could not be found') + + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split('.'))) +if version_info < (10, 8): + raise OSError( + 'Only OS X 10.8 and newer are supported, not %s.%s' % ( + version_info[0], version_info[1] + ) + ) + +Security = CDLL(security_path, use_errno=True) +CoreFoundation = CDLL(core_foundation_path, use_errno=True) + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [ + CFAllocatorRef, + CFDataRef + ] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [ + SecCertificateRef + ] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [ + OSStatus, + c_void_p + ] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef) + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef) + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [ + SecKeychainRef + ] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef) + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t)) + + Security.SSLSetIOFuncs.argtypes = [ + SSLContextRef, + SSLReadFunc, + SSLWriteFunc + ] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t + ] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [ + SSLContextRef, + CFArrayRef + ] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [ + SSLContextRef, + CFTypeRef, + Boolean + ] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [ + SSLContextRef, + SSLConnectionRef + ] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t + ] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [ + SSLContextRef + ] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t, + POINTER(c_size_t) + ] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t, + POINTER(c_size_t) + ] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [ + SSLContextRef + ] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(c_size_t) + ] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t) + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [ + SSLContextRef, + POINTER(c_size_t) + ] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t) + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite) + ] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol) + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [ + SSLContextRef, + POINTER(SecTrustRef) + ] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [ + SecTrustRef, + CFArrayRef + ] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [ + SecTrustRef, + Boolean + ] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [ + SecTrustRef, + POINTER(SecTrustResultType) + ] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [ + SecTrustRef + ] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [ + SecTrustRef, + CFIndex + ] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [ + SSLContextRef, + SSLSessionOption, + Boolean + ] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [ + SSLContextRef, + SSLProtocol + ] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [ + SSLContextRef, + SSLProtocol + ] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + Security.SecCopyErrorMessageString.argtypes = [ + OSStatus, + c_void_p + ] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, 'kSecImportExportPassphrase' + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, 'kSecImportItemIdentity' + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [ + CFTypeRef + ] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [ + CFTypeRef + ] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [ + CFTypeRef + ] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [ + CFStringRef, + CFStringEncoding + ] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [ + CFAllocatorRef, + c_char_p, + CFIndex + ] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [ + CFDataRef + ] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [ + CFDataRef + ] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [ + CFDictionaryRef, + CFTypeRef + ] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [ + CFMutableArrayRef, + c_void_p + ] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [ + CFArrayRef + ] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [ + CFArrayRef, + CFIndex + ] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, 'kCFAllocatorDefault' + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll(CoreFoundation, 'kCFTypeArrayCallBacks') + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, 'kCFTypeDictionaryKeyCallBacks' + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, 'kCFTypeDictionaryValueCallBacks' + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError('Error initializing ctypes') + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2 + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_CHACHA20_POLY1305_SHA256 = 0x1303 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/low_level.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000..b13cd9e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,346 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import re +import os +import ssl +import tempfile + +from .bindings import Security, CoreFoundation, CFConst + + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, + CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, + buffer, + 1024, + CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError('Error copying C string from CFStringRef') + string = buffer.value + if string is not None: + string = string.decode('utf-8') + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u'': + output = u'OSStatus %s' % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) + for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks) + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode('utf-8') + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode('utf-8') + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, + len(password), + password, + False, + None, + ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, 'rb') as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, + raw_filedata, + len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array) # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex( + result_array, index + ) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file( + keychain, file_path + ) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, + certificates[0], + ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/appengine.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/appengine.py new file mode 100644 index 0000000..9b42952 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/appengine.py @@ -0,0 +1,289 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service <https://cloud.google.com/appengine/docs/python/urlfetch>`_. + +Example usage:: + + from pip._vendor.urllib3 import PoolManager + from pip._vendor.urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations <https://cloud.google.com/appengine/docs/python/\ +urlfetch/#Python_Quotas_and_limits>`_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + <https://cloud.google.com/appengine/docs/python/sockets/\ + #limitations-and-restrictions>`_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +<https://cloud.google.com/appengine/docs/flexible/>`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import +import io +import logging +import warnings +from ..packages.six.moves.urllib.parse import urljoin + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + TimeoutError, + SSLError +) + +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.timeout import Timeout +from ..util.retry import Retry +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + <https://cloud.google.com/appengine/docs/python/urlfetch>`_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabtyes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__(self, headers=None, retries=None, validate_certificate=True, + urlfetch_retries=True): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment.") + + if is_prod_appengine_mvms(): + raise AppEnginePlatformError( + "Use normal urllib3.PoolManager instead of AppEngineManager" + "on Managed VMs, as using URLFetch is not necessary in " + "this environment.") + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", + AppEnginePlatformWarning) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen(self, method, url, body=None, headers=None, + retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = ( + redirect and + retries.redirect != 0 and + retries.total) + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if 'too large' in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", e) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if 'Too many redirects' in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", e) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if (self.urlfetch_retries and retries.raise_on_redirect): + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = 'GET' + + try: + retries = retries.increment(method, url, response=http_response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, redirect_url, body, headers, + retries=retries, redirect=redirect, + timeout=timeout, **response_kw) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.getheader('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) + retries.sleep(http_response) + return self.urlopen( + method, url, + body=body, headers=headers, + retries=retries, redirect=redirect, + timeout=timeout, **response_kw) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get('content-encoding') + + if content_encoding == 'deflate': + del urlfetch_resp.headers['content-encoding'] + + transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == 'chunked': + encodings = transfer_encoding.split(",") + encodings.remove('chunked') + urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int( + retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/ntlmpool.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000..8ea127c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/ntlmpool.py @@ -0,0 +1,111 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +from logging import getLogger +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from ..packages.six.moves.http_client import HTTPSConnection + + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = 'https' + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split('\\', 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s', + self.num_connections, self.host, self.authurl) + + headers = {'Connection': 'Keep-Alive'} + req_header = 'Authorization' + resp_header = 'www-authenticate' + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = ( + 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) + log.debug('Request headers: %s', headers) + conn.request('GET', self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.getheaders()) + log.debug('Response status: %s %s', res.status, res.reason) + log.debug('Response headers: %s', reshdr) + log.debug('Response data: %s [...]', res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(', ') + auth_header_value = None + for s in auth_header_values: + if s[:5] == 'NTLM ': + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception('Unexpected %s response header: %s' % + (resp_header, reshdr[resp_header])) + + # Send authentication message + ServerChallenge, NegotiateFlags = \ + ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, + self.user, + self.domain, + self.pw, + NegotiateFlags) + headers[req_header] = 'NTLM %s' % auth_msg + log.debug('Request headers: %s', headers) + 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 data: %s [...]', res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception('Server rejected request: wrong ' + 'username or password') + raise Exception('Wrong server response: %s %s' % + (res.status, res.reason)) + + res.fp = None + log.debug('Connection established') + return conn + + def urlopen(self, method, url, body=None, headers=None, retries=3, + redirect=True, assert_same_host=True): + if headers is None: + headers = {} + headers['Connection'] = 'Keep-Alive' + return super(NTLMConnectionPool, self).urlopen(method, url, body, + headers, retries, + redirect, + assert_same_host) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/pyopenssl.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000..363667c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/pyopenssl.py @@ -0,0 +1,466 @@ +""" +SSL with SNI_-support for Python 2. Follow these instructions if you would +like to verify SSL certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* pyOpenSSL (tested with 16.0.0) +* cryptography (minimum 1.3.4, from pyopenssl) +* idna (minimum 2.0, from cryptography) + +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. + +You can install them with the following command: + + pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this:: + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +If you want to configure the default list of supported cipher suites, you can +set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +""" +from __future__ import absolute_import + +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 +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): + pass + +from socket import timeout, error as SocketError +from io import BytesIO + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +import logging +import ssl +from ..packages import six +import sys + +from .. import util + +__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] + +# SNI always works. +HAS_SNI = True + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + +try: + _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) +except AttributeError: + pass + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: + OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = dict( + (v, k) for k, v in _stdlib_to_openssl_verify.items() +) + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3(): + 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' + + _validate_dependencies_met() + + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3(): + 'Undo monkey-patching by :func:`inject_into_urllib3`.' + + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met(): + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError("'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer.") + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError("'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer.") + + +def _dnsname_to_stdlib(name): + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + def idna_encode(name): + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + from pip._vendor import idna + + try: + for prefix in [u'*.', u'.']: + if name.startswith(prefix): + name = name[len(prefix):] + return prefix.encode('ascii') + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + name = idna_encode(name) + if name is None: + return None + elif sys.version_info >= (3, 0): + name = name.decode('utf-8') + return name + + +def get_subj_alt_name(peer_cert): + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + # Pass the cert to cryptography, which has much better APIs for this. + 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) + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName + ).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except (x509.DuplicateExtension, UnsupportedExtension, + x509.UnsupportedGeneralNameType, UnicodeError) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ('DNS', name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ('IP Address', str(name)) + for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket(object): + '''API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + ''' + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self._closed = False + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + return b'' + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError as e: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b'' + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout('The read operation timed out') + else: + return self.recv(*args, **kwargs) + else: + return data + + def recv_into(self, *args, **kwargs): + try: + return self.connection.recv_into(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + return 0 + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError as e: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout('The read operation timed out') + else: + return self.recv_into(*args, **kwargs) + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() + continue + except OpenSSL.SSL.SysCallError as e: + raise SocketError(str(e)) + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, + x509) + + return { + 'subject': ( + (('commonName', x509.get_subject().CN),), + ), + 'subjectAltName': get_subj_alt_name(x509) + } + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + def __init__(self, protocol): + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + + @property + def options(self): + return self._options + + @options.setter + def options(self, value): + self._options = value + self._ctx.set_options(value) + + @property + def verify_mode(self): + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value): + self._ctx.set_verify( + _stdlib_to_openssl_verify[value], + _verify_callback + ) + + def set_default_verify_paths(self): + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): + ciphers = ciphers.encode('utf-8') + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + if cafile is not None: + cafile = cafile.encode('utf-8') + if capath is not None: + capath = capath.encode('utf-8') + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password) + self._ctx.use_privatekey_file(keyfile or certfile) + + def wrap_socket(self, sock, server_side=False, + do_handshake_on_connect=True, suppress_ragged_eofs=True, + server_hostname=None): + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode('utf-8') + + if server_hostname is not None: + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout('select timed out') + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError('bad handshake: %r' % e) + break + + return WrappedSocket(cnx, sock) + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/securetransport.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/securetransport.py new file mode 100644 index 0000000..77cb59e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/securetransport.py @@ -0,0 +1,804 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import urllib3.contrib.securetransport + urllib3.contrib.securetransport.inject_into_urllib3() + +Happy TLSing! +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import threading +import weakref + +from .. import util +from ._securetransport.bindings import ( + Security, SecurityConst, CoreFoundation +) +from ._securetransport.low_level import ( + _assert_no_error, _cert_array_from_pem, _temporary_keychain, + _load_client_cert_chain +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +_protocol_to_min_max = { + ssl.PROTOCOL_SSLv23: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, SecurityConst.kSSLProtocol2 + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, SecurityConst.kSSLProtocol3 + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol1 + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, SecurityConst.kTLSProtocol11 + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12 + ) +if hasattr(ssl, "PROTOCOL_TLS"): + _protocol_to_min_max[ssl.PROTOCOL_TLS] = _protocol_to_min_max[ssl.PROTOCOL_SSLv23] + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, 'timed out') + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, 'timed out') + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, 'rb') as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust( + self.context, ctypes.byref(trust) + ) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate( + trust, ctypes.byref(trust_result) + ) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + # Ok, now we can look at what the result was. + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed + ) + if trust_result.value not in successes: + raise ssl.SSLError( + "certificate verify failed, error code: %d" % + trust_result.value + ) + + def handshake(self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode('utf-8') + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, + SecurityConst.kSSLSessionOptionBreakOnServerAuth, + True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate( + self.context, self._client_cert_chain + ) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if (result == SecurityConst.errSSLWouldBlock): + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in (SecurityConst.errSSLClosedGraceful, SecurityConst.errSSLClosedNoNotify): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError( + "SecureTransport only supports dumping binary certs" + ) + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust( + self.context, ctypes.byref(trust) + ) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): + 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): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError( + "SecureTransport doesn't support custom cipher strings" + ) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError( + "SecureTransport does not support cert directories" + ) + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def wrap_socket(self, sock, server_side=False, + do_handshake_on_connect=True, suppress_ragged_eofs=True, + server_hostname=None): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, self._verify, self._trust_bundle, + self._min_version, self._max_version, self._client_cert, + self._client_key, self._client_key_passphrase + ) + return wrapped_socket diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/socks.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/socks.py new file mode 100644 index 0000000..811e312 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/contrib/socks.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4 +- SOCKS4a +- SOCKS5 +- Usernames and passwords for the SOCKS proxy + +Known Limitations: + +- Currently PySocks does not support contacting remote websites via literal + IPv6 addresses. Any such connection attempt will fail. You must use a domain + name. +- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any + such connection attempt will fail. +""" +from __future__ import absolute_import + +try: + import socks +except ImportError: + import warnings + from ..exceptions import DependencyWarning + + warnings.warn(( + 'SOCKS support in urllib3 requires the installation of optional ' + 'dependencies: specifically, PySocks. For more information, see ' + 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies' + ), + DependencyWarning + ) + raise + +from socket import error as SocketError, timeout as SocketTimeout + +from ..connection import ( + HTTPConnection, HTTPSConnection +) +from ..connectionpool import ( + HTTPConnectionPool, HTTPSConnectionPool +) +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop('_socks_options') + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw = {} + if self.source_address: + extra_kw['source_address'] = self.source_address + + if self.socket_options: + extra_kw['socket_options'] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options['socks_version'], + proxy_addr=self._socks_options['proxy_host'], + proxy_port=self._socks_options['proxy_port'], + proxy_username=self._socks_options['username'], + proxy_password=self._socks_options['password'], + proxy_rdns=self._socks_options['rdns'], + timeout=self.timeout, + **extra_kw + ) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout) + ) + else: + raise NewConnectionError( + self, + "Failed to establish a new connection: %s" % error + ) + else: + raise NewConnectionError( + self, + "Failed to establish a new connection: %s" % e + ) + + except SocketError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e) + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + pool_classes_by_scheme = { + 'http': SOCKSHTTPConnectionPool, + 'https': SOCKSHTTPSConnectionPool, + } + + def __init__(self, proxy_url, username=None, password=None, + num_pools=10, headers=None, **connection_pool_kw): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(':') + if len(split) == 2: + username, password = split + if parsed.scheme == 'socks5': + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == 'socks5h': + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == 'socks4': + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == 'socks4a': + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError( + "Unable to determine SOCKS version from %s" % proxy_url + ) + + self.proxy_url = proxy_url + + socks_options = { + 'socks_version': socks_version, + 'proxy_host': parsed.host, + 'proxy_port': parsed.port, + 'username': username, + 'password': password, + 'rdns': rdns + } + connection_pool_kw['_socks_options'] = socks_options + + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/exceptions.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/exceptions.py new file mode 100644 index 0000000..7bbaa98 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/exceptions.py @@ -0,0 +1,246 @@ +from __future__ import absolute_import +from .packages.six.moves.http_client import ( + IncompleteRead as httplib_IncompleteRead +) +# Base Exceptions + + +class HTTPError(Exception): + "Base exception used by this module." + pass + + +class HTTPWarning(Warning): + "Base warning used by this module." + pass + + +class PoolError(HTTPError): + "Base exception for errors caused within a pool." + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + "Base exception for PoolErrors that have associated URLs." + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + "Raised when SSL certificate fails in an HTTPS connection." + pass + + +class ProxyError(HTTPError): + "Raised when the connection to a proxy fails." + pass + + +class DecodeError(HTTPError): + "Raised when automatic decoding based on Content-Type fails." + pass + + +class ProtocolError(HTTPError): + "Raised when something unexpected happens mid-request/response." + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % ( + url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + "Raised when an existing pool gets a request for a foreign host." + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """ Raised when passing an invalid state to a timeout """ + pass + + +class TimeoutError(HTTPError): + """ Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`. + """ + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + "Raised when a socket timeout occurs while receiving data from a server" + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + "Raised when a socket timeout occurs while connecting to a server" + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + pass + + +class EmptyPoolError(PoolError): + "Raised when a pool runs out of connections and no more are allowed." + pass + + +class ClosedPoolError(PoolError): + "Raised when a request enters a pool after the pool has been closed." + pass + + +class LocationValueError(ValueError, HTTPError): + "Raised when there is something wrong with a given URL input." + pass + + +class LocationParseError(LocationValueError): + "Raised when get_host or similar fails to parse the URL input." + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class ResponseError(HTTPError): + "Used as a container for an error reason supplied in a MaxRetryError." + GENERIC_ERROR = 'too many error responses' + SPECIFIC_ERROR = 'too many {status_code} error responses' + + +class SecurityWarning(HTTPWarning): + "Warned when performing security reducing actions" + pass + + +class SubjectAltNameWarning(SecurityWarning): + "Warned when connecting to a host with a certificate missing a SAN." + pass + + +class InsecureRequestWarning(SecurityWarning): + "Warned when making an unverified HTTPS request." + pass + + +class SystemTimeWarning(SecurityWarning): + "Warned when system time is suspected to be wrong" + pass + + +class InsecurePlatformWarning(SecurityWarning): + "Warned when certain SSL configuration is not available on a platform." + pass + + +class SNIMissingWarning(HTTPWarning): + "Warned when making a HTTPS request without SNI available." + pass + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + "Response needs to be chunked in order to read it as chunks." + pass + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be httplib.HTTPResponse like (have an fp attribute which + returns raw chunks) for read_chunked(). + """ + pass + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of http_client.IncompleteRead to allow int value + for `partial` to avoid creating large objects on streamed + reads. + """ + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) + + def __repr__(self): + return ('IncompleteRead(%i bytes read, ' + '%i more expected)' % (self.partial, self.expected)) + + +class InvalidHeader(HTTPError): + "The header provided was somehow invalid." + pass + + +class ProxySchemeUnknown(AssertionError, ValueError): + "ProxyManager does not support the supplied scheme" + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + message = "Not supported proxy scheme %s" % scheme + super(ProxySchemeUnknown, self).__init__(message) + + +class HeaderParsingError(HTTPError): + "Raised by assert_header_parsing, but we convert it to a log.warning statement." + def __init__(self, defects, unparsed_data): + message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) + super(HeaderParsingError, self).__init__(message) + + +class UnrewindableBodyError(HTTPError): + "urllib3 encountered an error when trying to rewind a body" + pass diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/fields.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/fields.py new file mode 100644 index 0000000..37fe64a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/fields.py @@ -0,0 +1,178 @@ +from __future__ import absolute_import +import email.utils +import mimetypes + +from .packages import six + + +def guess_content_type(filename, default='application/octet-stream'): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param(name, value): + """ + Helper function to format and quote a single header parameter. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows RFC 2231, as + suggested by RFC 2388 Section 4.4. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + if not any(ch in value for ch in '"\\\r\n'): + result = '%s="%s"' % (name, value) + try: + result.encode('ascii') + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + if not six.PY3 and isinstance(value, six.text_type): # Python 2: + value = value.encode('utf-8') + value = email.utils.encode_rfc2231(value, 'utf-8') + value = '%s*=%s' % (name, value) + return value + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. + :param headers: + An optional dict-like object of headers to initially use for the field. + """ + def __init__(self, name, data, filename=None, headers=None): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + + @classmethod + def from_tuples(cls, fieldname, value): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls(fieldname, data, filename=filename) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + return format_header_param(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return '; '.join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append('%s: %s' % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append('%s: %s' % (header_name, header_value)) + + lines.append('\r\n') + return '\r\n'.join(lines) + + def make_multipart(self, content_disposition=None, content_type=None, + content_location=None): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers['Content-Disposition'] = content_disposition or 'form-data' + self.headers['Content-Disposition'] += '; '.join([ + '', self._render_parts( + (('name', self._name), ('filename', self._filename)) + ) + ]) + self.headers['Content-Type'] = content_type + self.headers['Content-Location'] = content_location diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/filepost.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/filepost.py new file mode 100644 index 0000000..78f1e19 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/filepost.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import +import binascii +import codecs +import os + +from io import BytesIO + +from .packages import six +from .packages.six import b +from .fields import RequestField + +writer = codecs.lookup('utf-8')[3] + + +def choose_boundary(): + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + boundary = binascii.hexlify(os.urandom(16)) + if six.PY3: + boundary = boundary.decode('ascii') + return boundary + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b('--%s\r\n' % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b'\r\n') + + body.write(b('--%s--\r\n' % (boundary))) + + content_type = str('multipart/form-data; boundary=%s' % boundary) + + return body.getvalue(), content_type diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/__init__.py new file mode 100644 index 0000000..170e974 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import + +from . import ssl_match_hostname + +__all__ = ('ssl_match_hostname', ) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/makefile.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000..740db37 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/backports/makefile.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io + +from socket import SocketIO + + +def backport_makefile(self, mode="r", buffering=None, encoding=None, + errors=None, newline=None): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError( + "invalid mode %r (only r, w, b allowed)" % (mode,) + ) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/six.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/six.py new file mode 100644 index 0000000..190c023 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/six.py @@ -0,0 +1,868 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# 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 absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py new file mode 100644 index 0000000..d6594eb --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py @@ -0,0 +1,19 @@ +import sys + +try: + # Our match_hostname function is the same as 3.5's, so we only want to + # import the match_hostname function if it's at least that good. + if sys.version_info < (3, 5): + 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 CertificateError, match_hostname + except ImportError: + # Our vendored copy + from ._implementation import CertificateError, match_hostname + +# Not needed, but documenting what we provide. +__all__ = ('CertificateError', 'match_hostname') diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py new file mode 100644 index 0000000..970cf65 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py @@ -0,0 +1,156 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re +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. +try: + from pip._vendor import ipaddress +except ImportError: + ipaddress = None + +__version__ = '3.5.0.1' + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r'.') + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + obj = unicode(obj, encoding='ascii', errors='strict') + return obj + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED") + 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 + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case + host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: + raise + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == 'IP Address': + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/poolmanager.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/poolmanager.py new file mode 100644 index 0000000..fe5491c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/poolmanager.py @@ -0,0 +1,450 @@ +from __future__ import absolute_import +import collections +import functools +import logging + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connectionpool import port_by_scheme +from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods +from .util.url import parse_url +from .util.retry import Retry + + +__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', + 'ssl_version', 'ca_cert_dir', 'ssl_context') + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + 'key_scheme', # str + 'key_host', # str + 'key_port', # int + 'key_timeout', # int or float or Timeout + 'key_retries', # int or Retry + 'key_strict', # bool + 'key_block', # bool + 'key_source_address', # str + 'key_key_file', # str + 'key_cert_file', # str + 'key_cert_reqs', # str + 'key_ca_certs', # str + 'key_ssl_version', # str + 'key_ca_cert_dir', # str + 'key_ssl_context', # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + 'key_maxsize', # int + 'key_headers', # dict + 'key__proxy', # parsed proxy url + 'key__proxy_headers', # dict + 'key_socket_options', # list of (level (int), optname (int), value (int or str)) tuples + 'key__socks_options', # dict + 'key_assert_hostname', # bool or string + 'key_assert_fingerprint', # str + 'key_server_hostname', #str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple('PoolKey', _key_fields) + + +def _default_key_normalizer(key_class, request_context): + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context['scheme'] = context['scheme'].lower() + context['host'] = context['host'].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ('headers', '_proxy_headers', '_socks_options'): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get('socket_options') + if socket_opts is not None: + context['socket_options'] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context['key_' + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + 'http': functools.partial(_default_key_normalizer, PoolKey), + 'https': functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = { + 'http': HTTPConnectionPool, + 'https': HTTPSConnectionPool, +} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, + dispose_func=lambda p: p.close()) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port, request_context=None): + """ + Create a new :class:`ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ('scheme', 'host', 'port'): + request_context.pop(key, None) + + if scheme == 'http': + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + """ + Get a :class:`ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context['scheme'] = scheme or 'http' + if not port: + port = port_by_scheme.get(request_context['scheme'].lower(), 80) + request_context['port'] = port + request_context['host'] = host + + return self.connection_from_context(request_context) + + def connection_from_context(self, request_context): + """ + Get a :class:`ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + scheme = request_context['scheme'].lower() + pool_key_constructor = self.key_fn_by_scheme[scheme] + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key(self, pool_key, request_context=None): + """ + Get a :class:`ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context['scheme'] + host = request_context['host'] + port = request_context['port'] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url, pool_kwargs=None): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host(u.host, port=u.port, scheme=u.scheme, + pool_kwargs=pool_kwargs) + + def _merge_pool_kwargs(self, override): + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw['assert_same_host'] = False + kw['redirect'] = False + + if 'headers' not in kw: + kw['headers'] = self.headers.copy() + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = 'GET' + + retries = kw.get('retries') + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if (retries.remove_headers_on_redirect + and not conn.is_same_host(redirect_location)): + for header in retries.remove_headers_on_redirect: + kw['headers'].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + raise + return response + + kw['retries'] = retries + kw['redirect'] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__(self, proxy_url, num_pools=10, headers=None, + proxy_headers=None, **connection_pool_kw): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, + proxy_url.port) + proxy = parse_url(proxy_url) + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + + connection_pool_kw['_proxy'] = self.proxy + connection_pool_kw['_proxy_headers'] = self.proxy_headers + + super(ProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {'Accept': '*/*'} + + netloc = parse_url(url).netloc + if netloc: + headers_['Host'] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + + if u.scheme == "http": + # For proxied HTTPS requests, httplib sets the necessary headers + # on the CONNECT to the proxy. For HTTP, we'll definitely + # need to set 'Host' at the very least. + headers = kw.get('headers', self.headers) + kw['headers'] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/request.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/request.py new file mode 100644 index 0000000..8f2f44b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/request.py @@ -0,0 +1,150 @@ +from __future__ import absolute_import + +from .filepost import encode_multipart_formdata +from .packages.six.moves.urllib.parse import urlencode + + +__all__ = ['RequestMethods'] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`~urllib3.connectionpool.HTTPConnectionPool` and + :class:`~urllib3.poolmanager.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {'DELETE', 'GET', 'HEAD', 'OPTIONS'} + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen(self, method, url, body=None, headers=None, + encode_multipart=True, multipart_boundary=None, + **kw): # Abstract + raise NotImplementedError("Classes extending RequestMethods must implement " + "their own ``urlopen`` method.") + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + urlopen_kw['request_url'] = url + + if method in self._encode_url_methods: + return self.request_encode_url(method, url, fields=fields, + headers=headers, + **urlopen_kw) + else: + return self.request_encode_body(method, url, fields=fields, + headers=headers, + **urlopen_kw) + + def request_encode_url(self, method, url, fields=None, headers=None, + **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {'headers': headers} + extra_kw.update(urlopen_kw) + + if fields: + url += '?' + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body(self, method, url, fields=None, headers=None, + encode_multipart=True, multipart_boundary=None, + **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :meth:`urllib.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {'headers': {}} + + if fields: + if 'body' in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one.") + + if encode_multipart: + body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) + else: + body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' + + extra_kw['body'] = body + extra_kw['headers'] = {'Content-Type': content_type} + + extra_kw['headers'].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/response.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/response.py new file mode 100644 index 0000000..c112690 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/response.py @@ -0,0 +1,705 @@ +from __future__ import absolute_import +from contextlib import contextmanager +import zlib +import io +import logging +from socket import timeout as SocketTimeout +from socket import error as SocketError + +from ._collections import HTTPHeaderDict +from .exceptions import ( + BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, + ResponseNotChunked, IncompleteRead, InvalidHeader +) +from .packages.six import string_types as basestring, PY3 +from .packages.six.moves import http_client as httplib +from .connection import HTTPException, BaseSSLError +from .util.response import is_fp_closed, is_response_to_head + +log = logging.getLogger(__name__) + + +class DeflateDecoder(object): + + def __init__(self): + self._first_try = True + self._data = b'' + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoderState(object): + + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(object): + + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + +class MultiDecoder(object): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(',')] + + def flush(self): + return self._decoders[0].flush() + + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode): + if ',' in mode: + return MultiDecoder(mode) + + if mode == 'gzip': + return GzipDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible to httplib's HTTPResponse but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in httplib.HTTPResponse: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an httplib.HTTPResponse + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + CONTENT_DECODERS = ['gzip', 'deflate'] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__(self, body='', headers=None, status=0, version=0, reason=None, + strict=0, preload_content=True, decode_content=True, + original_response=None, pool=None, connection=None, msg=None, + retries=None, enforce_content_length=False, + request_method=None, request_url=None): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries + self.enforce_content_length = enforce_content_length + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + self._request_url = request_url + + if body and isinstance(body, (basestring, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, 'read'): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get('transfer-encoding', '').lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get('location') + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + @property + def data(self): + # For backwords-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + @property + def connection(self): + return self._connection + + def isclosed(self): + return is_fp_closed(self._fp) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``HTTPResponse.read`` if bytes + are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method): + """ + Set initial length value for Response content if available. + """ + length = self.headers.get('content-length') + + if length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning("Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked.") + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = set([int(val) for val in length.split(',')]) + if len(lengths) > 1: + raise InvalidHeader("Content-Length contained multiple " + "unmatching values (%s)" % length) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': + length = 0 + + return length + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get('content-encoding', '').lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif ',' in content_encoding: + encodings = [e.strip() for e in content_encoding.split(',') if e.strip() in self.CONTENT_DECODERS] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + try: + if decode_content and self._decoder: + data = self._decoder.decompress(data) + except (IOError, zlib.error) as e: + content_encoding = self.headers.get('content-encoding', '').lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, e) + + if flush_decoder and decode_content: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b'') + return buf + self._decoder.flush() + + return b'' + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if 'read operation timed out' not in str(e): # Defensive: + # This shouldn't happen but just in case we're missing an edge + # case, let's avoid swallowing SSL errors. + raise + + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError('Connection broken: %r' % e, e) + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`httplib.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + data = None + + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + data = self._fp.read() + flush_decoder = True + else: + cache_content = False + data = self._fp.read(amt) + if amt != 0 and not data: # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in (0, None): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2**16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`httplib.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if PY3: # Python 3 + headers = HTTPHeaderDict(headers.items()) + else: # Python 2 + headers = HTTPHeaderDict.from_httplib(headers) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, 'strict', 0) + resp = ResponseCls(body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw) + return resp + + # Backwards-compatibility methods for httplib.HTTPResponse + def getheaders(self): + return self.headers + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + if self._connection: + self._connection.close() + + @property + def closed(self): + if self._fp is None: + return True + elif hasattr(self._fp, 'isclosed'): + return self._fp.isclosed() + elif hasattr(self._fp, 'closed'): + return self._fp.closed + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError("The file-like object this HTTPResponse is wrapped " + "around has no file descriptor") + + def flush(self): + if self._fp is not None and hasattr(self._fp, 'flush'): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[:len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): + """ + Checks if the underlying file-like object looks like a + httplib.HTTPResponse object. We do this by testing for the fp + attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, 'fp') + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b';', 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise httplib.IncompleteRead(line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing.") + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be httplib.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks.") + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: + return + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode(chunk, decode_content=decode_content, + flush_decoder=False) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b'\r\n': + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + def geturl(self): + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/__init__.py new file mode 100644 index 0000000..2f2770b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/__init__.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import make_headers +from .response import is_fp_closed +from .ssl_ import ( + SSLContext, + HAS_SNI, + IS_PYOPENSSL, + IS_SECURETRANSPORT, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import ( + current_time, + Timeout, +) + +from .retry import Retry +from .url import ( + get_host, + parse_url, + split_first, + Url, +) +from .wait import ( + wait_for_read, + wait_for_write +) + +__all__ = ( + 'HAS_SNI', + 'IS_PYOPENSSL', + 'IS_SECURETRANSPORT', + 'SSLContext', + 'Retry', + 'Timeout', + 'Url', + 'assert_fingerprint', + 'current_time', + 'is_connection_dropped', + 'is_fp_closed', + 'get_host', + 'parse_url', + 'make_headers', + 'resolve_cert_reqs', + 'resolve_ssl_version', + 'split_first', + 'ssl_wrap_socket', + 'wait_for_read', + 'wait_for_write' +) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/connection.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/connection.py new file mode 100644 index 0000000..5ad70b2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/connection.py @@ -0,0 +1,134 @@ +from __future__ import absolute_import +import socket +from .wait import NoWayToWaitForSocketError, wait_for_read +from ..contrib import _appengine_environ + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`httplib.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, 'sock', False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, socket_options=None): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith('['): + host = host.strip('[]') + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family(): + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host): + """ Returns True if the system can bind an IPv6 address. """ + sock = None + has_ipv6 = False + + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/shazow/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6('::1') diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/queue.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/queue.py new file mode 100644 index 0000000..d3d379a --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/queue.py @@ -0,0 +1,21 @@ +import collections +from ..packages import six +from ..packages.six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/request.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/request.py new file mode 100644 index 0000000..3ddfcd5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/request.py @@ -0,0 +1,118 @@ +from __future__ import absolute_import +from base64 import b64encode + +from ..packages.six import b, integer_types +from ..exceptions import UnrewindableBodyError + +ACCEPT_ENCODING = 'gzip,deflate' +_FAILEDTELL = object() + + +def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, + basic_auth=None, proxy_basic_auth=None, disable_cache=None): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ','.join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers['accept-encoding'] = accept_encoding + + if user_agent: + headers['user-agent'] = user_agent + + if keep_alive: + headers['connection'] = 'keep-alive' + + if basic_auth: + headers['authorization'] = 'Basic ' + \ + b64encode(b(basic_auth)).decode('utf-8') + + if proxy_basic_auth: + headers['proxy-authorization'] = 'Basic ' + \ + b64encode(b(proxy_basic_auth)).decode('utf-8') + + if disable_cache: + headers['cache-control'] = 'no-cache' + + return headers + + +def set_file_position(body, pos): + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, 'tell', None) is not None: + try: + pos = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body, body_pos): + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, 'seek', None) + if body_seek is not None and isinstance(body_pos, integer_types): + try: + body_seek(body_pos) + except (IOError, OSError): + raise UnrewindableBodyError("An error occurred when rewinding request " + "body for redirect/retry.") + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError("Unable to record file position for rewinding " + "request body during a redirect/retry.") + else: + raise ValueError("body_pos must be of type integer, " + "instead it was %s." % type(body_pos)) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/response.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/response.py new file mode 100644 index 0000000..3d54864 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/response.py @@ -0,0 +1,87 @@ +from __future__ import absolute_import +from ..packages.six.moves import http_client as httplib + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param headers: Headers to verify. + :type headers: `httplib.HTTPMessage`. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError('expected httplib.Message, got {0}.'.format( + type(headers))) + + defects = getattr(headers, 'defects', None) + get_payload = getattr(headers, 'get_payload', None) + + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param conn: + :type conn: :class:`httplib.HTTPResponse` + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == 'HEAD' diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/retry.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/retry.py new file mode 100644 index 0000000..e7d0abd --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/retry.py @@ -0,0 +1,411 @@ +from __future__ import absolute_import +import time +import logging +from collections import namedtuple +from itertools import takewhile +import email +import re + +from ..exceptions import ( + ConnectTimeoutError, + MaxRetryError, + ProtocolError, + ReadTimeoutError, + ResponseError, + InvalidHeader, +) +from ..packages import six + + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +RequestHistory = namedtuple('RequestHistory', ["method", "url", "error", + "status", "redirect_location"]) + + +class Retry(object): + """ Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. It's a good idea to set this to some sensibly-high value to + account for unexpected edge cases and avoid infinite retry loops. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param iterable method_whitelist: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + + Set to a ``False`` value to retry on any verb. + + :param iterable status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``method_whitelist`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of total retries} - 1)) + + 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`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + DEFAULT_METHOD_WHITELIST = frozenset([ + 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) + + #: Maximum backoff time. + BACKOFF_MAX = 120 + + def __init__(self, total=10, connect=None, read=None, redirect=None, status=None, + method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, + backoff_factor=0, raise_on_redirect=True, raise_on_status=True, + history=None, respect_retry_after_header=True, + remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + + self.total = total + self.connect = connect + self.read = read + self.status = status + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or tuple() + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = remove_headers_on_redirect + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, read=self.read, redirect=self.redirect, status=self.status, + method_whitelist=self.method_whitelist, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect + ) + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """ Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self): + """ Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, + reversed(self.history)))) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + return min(self.BACKOFF_MAX, backoff_value) + + def parse_retry_after(self, retry_after): + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate(retry_after) + if retry_date_tuple is None: + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + retry_date = time.mktime(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + def get_retry_after(self, response): + """ Get the value of Retry-After in seconds. """ + + retry_after = response.getheader("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response=None): + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self): + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response=None): + """ Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err): + """ Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """ Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method): + """ Checks if a given HTTP method should be retried upon, depending if + it is included on the method whitelist. + """ + if self.method_whitelist and method.upper() not in self.method_whitelist: + return False + + return True + + def is_retry(self, method, status_code, has_retry_after=False): + """ Is this method/status code retryable? (Based on whitelists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return (self.total and self.respect_retry_after_header and + has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES)) + + def is_exhausted(self): + """ Are we out of retries? """ + retry_counts = (self.total, self.connect, self.read, self.redirect, self.status) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment(self, method=None, url=None, response=None, error=None, + _pool=None, _stacktrace=None): + """ Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + cause = 'unknown' + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = 'too many redirects' + redirect_location = response.get_redirect_location() + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format( + status_code=response.status) + status = response.status + + history = self.history + (RequestHistory(method, url, error, status, redirect_location),) + + new_retry = self.new( + total=total, + connect=connect, read=read, redirect=redirect, status=status_count, + history=history) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self): + return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' + 'read={self.read}, redirect={self.redirect}, status={self.status})').format( + cls=type(self), self=self) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/ssl_.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/ssl_.py new file mode 100644 index 0000000..dfc553f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/ssl_.py @@ -0,0 +1,381 @@ +from __future__ import absolute_import +import errno +import warnings +import hmac +import socket + +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning +from ..packages import six + + +SSLContext = None +HAS_SNI = False +IS_PYOPENSSL = False +IS_SECURETRANSPORT = False + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + 32: md5, + 40: sha1, + 64: sha256, +} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for l, r in zip(bytearray(a), bytearray(b)): + result |= l ^ r + return result == 0 + + +_const_compare_digest = getattr(hmac, 'compare_digest', + _const_compare_digest_backport) + + +try: # Test for SSL features + import ssl + from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + + +try: + from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + + +# Python 2.7 doesn't have inet_pton on non-Linux so we fallback on inet_aton in +# those cases. This means that we can only detect IPv4 addresses in this case. +if hasattr(socket, 'inet_pton'): + inet_pton = socket.inet_pton +else: + # Maybe we can use ipaddress if the user has urllib3[secure]? + try: + from pip._vendor import ipaddress + + def inet_pton(_, host): + if isinstance(host, bytes): + host = host.decode('ascii') + return ipaddress.ip_address(host) + + except ImportError: # Platform-specific: Non-Linux + def inet_pton(_, host): + return socket.inet_aton(host) + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - Prefer TLS 1.3 cipher suites +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs and DSS for security reasons. +DEFAULT_CIPHERS = ':'.join([ + 'TLS13-AES-256-GCM-SHA384', + 'TLS13-CHACHA20-POLY1305-SHA256', + 'TLS13-AES-128-GCM-SHA256', + 'ECDH+AESGCM', + 'ECDH+CHACHA20', + 'DH+AESGCM', + 'DH+CHACHA20', + 'ECDH+AES256', + 'DH+AES256', + 'ECDH+AES128', + 'DH+AES', + 'RSA+AESGCM', + 'RSA+AES', + '!aNULL', + '!eNULL', + '!MD5', +]) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + import sys + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + 'A true SSLContext object is not available. This prevents ' + 'urllib3 from configuring SSL appropriately and may cause ' + 'certain SSL connections to fail. You can upgrade to a newer ' + 'version of Python to solve this. For more information, see ' + 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' + '#ssl-warnings', + InsecurePlatformWarning + ) + kwargs = { + 'keyfile': self.keyfile, + 'certfile': self.certfile, + 'ca_certs': self.ca_certs, + 'cert_reqs': self.verify_mode, + 'ssl_version': self.protocol, + 'server_side': server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(':', '').lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError( + 'Fingerprint of invalid length: {0}'.format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' + .format(fingerprint, hexlify(cert_digest))) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_NONE`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_NONE + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'CERT_' + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_SSLv23 + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'PROTOCOL_' + candidate) + return res + + return candidate + + +def create_urllib3_context(ssl_version=None, cert_reqs=None, + options=None, ciphers=None): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from pip._vendor.urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) + + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + + context.options |= options + + context.verify_mode = cert_reqs + if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + return context + + +def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None, ciphers=None, ssl_context=None, + ca_cert_dir=None): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. + context = create_urllib3_context(ssl_version, cert_reqs, + ciphers=ciphers) + + if ca_certs or ca_cert_dir: + try: + context.load_verify_locations(ca_certs, ca_cert_dir) + except IOError as e: # Platform-specific: Python 2.7 + raise SSLError(e) + # Py33 raises FileNotFoundError which subclasses OSError + # These are not equivalent unless we check the errno attribute + except OSError as e: # Platform-specific: Python 3.3 and beyond + if e.errno == errno.ENOENT: + raise SSLError(e) + raise + elif getattr(context, 'load_default_certs', None) is not None: + # try to load OS default certs; works well on Windows (require Python3.4+) + context.load_default_certs() + + if certfile: + context.load_cert_chain(certfile, keyfile) + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + # We shouldn't warn the user if SNI isn't available but we would + # not be using SNI anyways due to IP address for server_hostname. + if ((server_hostname is not None and not is_ipaddress(server_hostname)) + or IS_SECURETRANSPORT): + if HAS_SNI and server_hostname is not None: + return context.wrap_socket(sock, server_hostname=server_hostname) + + warnings.warn( + 'An HTTPS request has been made, but the SNI (Server Name ' + 'Indication) extension to TLS is not available on this platform. ' + 'This may cause the server to present an incorrect TLS ' + 'certificate, which can cause validation failures. You can upgrade to ' + 'a newer version of Python to solve this. For more information, see ' + 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' + '#ssl-warnings', + SNIMissingWarning + ) + + return context.wrap_socket(sock) + + +def is_ipaddress(hostname): + """Detects whether the hostname given is an IP address. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if six.PY3 and isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode('ascii') + + families = [socket.AF_INET] + if hasattr(socket, 'AF_INET6'): + families.append(socket.AF_INET6) + + for af in families: + try: + inet_pton(af, hostname) + except (socket.error, ValueError, OSError): + pass + else: + return True + return False diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/timeout.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/timeout.py new file mode 100644 index 0000000..cec817e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/timeout.py @@ -0,0 +1,242 @@ +from __future__ import absolute_import +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT +import time + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) + + +class Timeout(object): + """ Timeout configuration. + + Timeouts can be defined as a default for a pool:: + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``:: + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: integer, float, or None + + :param connect: + The maximum amount of time to wait for a connection attempt to a server + to succeed. Omitting the parameter will default the connect timeout to + the system default, probably `the global default timeout in socket.py + <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. + None will set an infinite timeout for connection attempts. + + :type connect: integer, float, or None + + :param read: + The maximum amount of time to wait between consecutive + read operations for a response from the server. Omitting + the parameter will default the read timeout to the system + default, probably `the global default timeout in socket.py + <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. + None will set an infinite timeout. + + :type read: integer, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, 'connect') + self._read = self._validate_timeout(read, 'read') + self.total = self._validate_timeout(total, 'total') + self._start_connect = None + + def __str__(self): + return '%s(connect=%r, read=%r, total=%r)' % ( + type(self).__name__, self._connect, self._read, self.total) + + @classmethod + def _validate_timeout(cls, value, name): + """ Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError("Timeout cannot be a boolean value. It must " + "be an int, float or None.") + try: + float(value) + except (TypeError, ValueError): + raise ValueError("Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value)) + + try: + if value <= 0: + raise ValueError("Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value)) + except TypeError: # Python 3 + raise ValueError("Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value)) + + return value + + @classmethod + def from_float(cls, timeout): + """ Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """ Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, + total=self.total) + + def start_connect(self): + """ Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """ Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError("Can't get connect duration for timer " + "that has not started.") + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """ Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """ Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if (self.total is not None and + self.total is not self.DEFAULT_TIMEOUT and + self._read is not None and + self._read is not self.DEFAULT_TIMEOUT): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), + self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/url.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/url.py new file mode 100644 index 0000000..6b6f996 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/url.py @@ -0,0 +1,230 @@ +from __future__ import absolute_import +from collections import namedtuple + +from ..exceptions import LocationParseError + + +url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +NORMALIZABLE_SCHEMES = ('http', 'https', None) + + +class Url(namedtuple('Url', url_attrs)): + """ + Datastructure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + __slots__ = () + + def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, + query=None, fragment=None): + if path and not path.startswith('/'): + path = '/' + path + if scheme: + scheme = scheme.lower() + if host and scheme in NORMALIZABLE_SCHEMES: + host = host.lower() + return super(Url, cls).__new__(cls, scheme, auth, host, port, path, + query, fragment) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or '/' + + if self.query is not None: + uri += '?' + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return '%s:%d' % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = '' + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + '://' + if auth is not None: + url += auth + '@' + if host is not None: + url += host + if port is not None: + url += ':' + str(port) + if path is not None: + url += path + if query is not None: + url += '?' + query + if fragment is not None: + url += '#' + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, '', None + + return s[:min_idx], s[min_idx + 1:], min_delim + + +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. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + + # While this code has overlap with stdlib's urlparse, it is much + # simplified for our needs and less annoying. + # Additionally, this implementations does silly things to be optimal + # on CPython. + + if not url: + # Empty + return Url() + + scheme = None + auth = None + host = None + port = None + path = None + fragment = None + query = None + + # Scheme + if '://' in url: + scheme, url = url.split('://', 1) + + # Find the earliest Authority Terminator + # (http://tools.ietf.org/html/rfc3986#section-3.2) + url, path_, delim = split_first(url, ['/', '?', '#']) + + if delim: + # Reassemble the path + path = delim + path_ + + # Auth + if '@' in url: + # Last '@' denotes end of auth part + auth, url = url.rsplit('@', 1) + + # IPv6 + if url and url[0] == '[': + host, url = url.split(']', 1) + host += ']' + + # Port + if ':' in url: + _host, port = url.split(':', 1) + + if not host: + host = _host + + if port: + # If given, ports must be integers. No whitespace, no plus or + # minus prefixes, no non-integer digits such as ^2 (superscript). + if not port.isdigit(): + raise LocationParseError(url) + try: + port = int(port) + except ValueError: + raise LocationParseError(url) + else: + # Blank ports are cool, too. (rfc3986#section-3.2.3) + port = None + + elif not host and url: + host = url + + if not path: + return Url(scheme, auth, host, port, path, query, fragment) + + # Fragment + if '#' in path: + path, fragment = path.split('#', 1) + + # Query + if '?' in path: + path, query = path.split('?', 1) + + return Url(scheme, auth, host, port, path, query, fragment) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or 'http', p.hostname, p.port diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/wait.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/wait.py new file mode 100644 index 0000000..4db71ba --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/urllib3/util/wait.py @@ -0,0 +1,150 @@ +import errno +from functools import partial +import select +import sys +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = _retry_on_intr(fn, timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t): + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + _retry_on_intr(poll_obj.poll, 0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket(*args, **kwargs): + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) + + +def wait_for_read(sock, timeout=None): + """ Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock, timeout=None): + """ Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/__init__.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/__init__.py new file mode 100644 index 0000000..d21d697 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/__init__.py @@ -0,0 +1,342 @@ +# coding: utf-8 +""" + + webencodings + ~~~~~~~~~~~~ + + This is a Python implementation of the `WHATWG Encoding standard + <http://encoding.spec.whatwg.org/>`. See README for details. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +import codecs + +from .labels import LABELS + + +VERSION = '0.5.1' + + +# Some names in Encoding are not valid Python aliases. Remap these. +PYTHON_NAMES = { + 'iso-8859-8-i': 'iso-8859-8', + 'x-mac-cyrillic': 'mac-cyrillic', + 'macintosh': 'mac-roman', + 'windows-874': 'cp874'} + +CACHE = {} + + +def ascii_lower(string): + r"""Transform (only) ASCII letters to lower case: A-Z is mapped to a-z. + + :param string: An Unicode string. + :returns: A new Unicode string. + + This is used for `ASCII case-insensitive + <http://encoding.spec.whatwg.org/#ascii-case-insensitive>`_ + matching of encoding labels. + The same matching is also used, among other things, + for `CSS keywords <http://dev.w3.org/csswg/css-values/#keywords>`_. + + This is different from the :meth:`~py:str.lower` method of Unicode strings + which also affect non-ASCII characters, + sometimes mapping them into the ASCII range: + + >>> keyword = u'Bac\N{KELVIN SIGN}ground' + >>> assert keyword.lower() == u'background' + >>> assert ascii_lower(keyword) != keyword.lower() + >>> assert ascii_lower(keyword) == u'bac\N{KELVIN SIGN}ground' + + """ + # This turns out to be faster than unicode.translate() + return string.encode('utf8').lower().decode('utf8') + + +def lookup(label): + """ + Look for an encoding by its label. + This is the spec’s `get an encoding + <http://encoding.spec.whatwg.org/#concept-encoding-get>`_ algorithm. + Supported labels are listed there. + + :param label: A string. + :returns: + An :class:`Encoding` object, or :obj:`None` for an unknown label. + + """ + # Only strip ASCII whitespace: U+0009, U+000A, U+000C, U+000D, and U+0020. + label = ascii_lower(label.strip('\t\n\f\r ')) + name = LABELS.get(label) + if name is None: + return None + encoding = CACHE.get(name) + if encoding is None: + if name == 'x-user-defined': + from .x_user_defined import codec_info + else: + python_name = PYTHON_NAMES.get(name, name) + # Any python_name value that gets to here should be valid. + codec_info = codecs.lookup(python_name) + encoding = Encoding(name, codec_info) + CACHE[name] = encoding + return encoding + + +def _get_encoding(encoding_or_label): + """ + Accept either an encoding object or label. + + :param encoding: An :class:`Encoding` object or a label string. + :returns: An :class:`Encoding` object. + :raises: :exc:`~exceptions.LookupError` for an unknown label. + + """ + if hasattr(encoding_or_label, 'codec_info'): + return encoding_or_label + + encoding = lookup(encoding_or_label) + if encoding is None: + raise LookupError('Unknown encoding label: %r' % encoding_or_label) + return encoding + + +class Encoding(object): + """Reresents a character encoding such as UTF-8, + that can be used for decoding or encoding. + + .. attribute:: name + + Canonical name of the encoding + + .. attribute:: codec_info + + The actual implementation of the encoding, + a stdlib :class:`~codecs.CodecInfo` object. + See :func:`codecs.register`. + + """ + def __init__(self, name, codec_info): + self.name = name + self.codec_info = codec_info + + def __repr__(self): + return '<Encoding %s>' % self.name + + +#: The UTF-8 encoding. Should be used for new content and formats. +UTF8 = lookup('utf-8') + +_UTF16LE = lookup('utf-16le') +_UTF16BE = lookup('utf-16be') + + +def decode(input, fallback_encoding, errors='replace'): + """ + Decode a single string. + + :param input: A byte string + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :return: + A ``(output, encoding)`` tuple of an Unicode string + and an :obj:`Encoding`. + + """ + # Fail early if `encoding` is an invalid label. + fallback_encoding = _get_encoding(fallback_encoding) + bom_encoding, input = _detect_bom(input) + encoding = bom_encoding or fallback_encoding + return encoding.codec_info.decode(input, errors)[0], encoding + + +def _detect_bom(input): + """Return (bom_encoding, input), with any BOM removed from the input.""" + if input.startswith(b'\xFF\xFE'): + return _UTF16LE, input[2:] + if input.startswith(b'\xFE\xFF'): + return _UTF16BE, input[2:] + if input.startswith(b'\xEF\xBB\xBF'): + return UTF8, input[3:] + return None, input + + +def encode(input, encoding=UTF8, errors='strict'): + """ + Encode a single string. + + :param input: An Unicode string. + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :return: A byte string. + + """ + return _get_encoding(encoding).codec_info.encode(input, errors)[0] + + +def iter_decode(input, fallback_encoding, errors='replace'): + """ + "Pull"-based decoder. + + :param input: + An iterable of byte strings. + + The input is first consumed just enough to determine the encoding + based on the precense of a BOM, + then consumed on demand when the return value is. + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :returns: + An ``(output, encoding)`` tuple. + :obj:`output` is an iterable of Unicode strings, + :obj:`encoding` is the :obj:`Encoding` that is being used. + + """ + + decoder = IncrementalDecoder(fallback_encoding, errors) + generator = _iter_decode_generator(input, decoder) + encoding = next(generator) + return generator, encoding + + +def _iter_decode_generator(input, decoder): + """Return a generator that first yields the :obj:`Encoding`, + then yields output chukns as Unicode strings. + + """ + decode = decoder.decode + input = iter(input) + for chunck in input: + output = decode(chunck) + if output: + assert decoder.encoding is not None + yield decoder.encoding + yield output + break + else: + # Input exhausted without determining the encoding + output = decode(b'', final=True) + assert decoder.encoding is not None + yield decoder.encoding + if output: + yield output + return + + for chunck in input: + output = decode(chunck) + if output: + yield output + output = decode(b'', final=True) + if output: + yield output + + +def iter_encode(input, encoding=UTF8, errors='strict'): + """ + “Pull”-based encoder. + + :param input: An iterable of Unicode strings. + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + :returns: An iterable of byte strings. + + """ + # Fail early if `encoding` is an invalid label. + encode = IncrementalEncoder(encoding, errors).encode + return _iter_encode_generator(input, encode) + + +def _iter_encode_generator(input, encode): + for chunck in input: + output = encode(chunck) + if output: + yield output + output = encode('', final=True) + if output: + yield output + + +class IncrementalDecoder(object): + """ + “Push”-based decoder. + + :param fallback_encoding: + An :class:`Encoding` object or a label string. + The encoding to use if :obj:`input` does note have a BOM. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + + """ + def __init__(self, fallback_encoding, errors='replace'): + # Fail early if `encoding` is an invalid label. + self._fallback_encoding = _get_encoding(fallback_encoding) + self._errors = errors + self._buffer = b'' + self._decoder = None + #: The actual :class:`Encoding` that is being used, + #: or :obj:`None` if that is not determined yet. + #: (Ie. if there is not enough input yet to determine + #: if there is a BOM.) + self.encoding = None # Not known yet. + + def decode(self, input, final=False): + """Decode one chunk of the input. + + :param input: A byte string. + :param final: + Indicate that no more input is available. + Must be :obj:`True` if this is the last call. + :returns: An Unicode string. + + """ + decoder = self._decoder + if decoder is not None: + return decoder(input, final) + + input = self._buffer + input + encoding, input = _detect_bom(input) + if encoding is None: + if len(input) < 3 and not final: # Not enough data yet. + self._buffer = input + return '' + else: # No BOM + encoding = self._fallback_encoding + decoder = encoding.codec_info.incrementaldecoder(self._errors).decode + self._decoder = decoder + self.encoding = encoding + return decoder(input, final) + + +class IncrementalEncoder(object): + """ + “Push”-based encoder. + + :param encoding: An :class:`Encoding` object or a label string. + :param errors: Type of error handling. See :func:`codecs.register`. + :raises: :exc:`~exceptions.LookupError` for an unknown encoding label. + + .. method:: encode(input, final=False) + + :param input: An Unicode string. + :param final: + Indicate that no more input is available. + Must be :obj:`True` if this is the last call. + :returns: A byte string. + + """ + def __init__(self, encoding=UTF8, errors='strict'): + encoding = _get_encoding(encoding) + self.encode = encoding.codec_info.incrementalencoder(errors).encode diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/labels.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/labels.py new file mode 100644 index 0000000..29cbf91 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/labels.py @@ -0,0 +1,231 @@ +""" + + webencodings.labels + ~~~~~~~~~~~~~~~~~~~ + + Map encoding labels to their name. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +# XXX Do not edit! +# This file is automatically generated by mklabels.py + +LABELS = { + 'unicode-1-1-utf-8': 'utf-8', + 'utf-8': 'utf-8', + 'utf8': 'utf-8', + '866': 'ibm866', + 'cp866': 'ibm866', + 'csibm866': 'ibm866', + 'ibm866': 'ibm866', + 'csisolatin2': 'iso-8859-2', + 'iso-8859-2': 'iso-8859-2', + 'iso-ir-101': 'iso-8859-2', + 'iso8859-2': 'iso-8859-2', + 'iso88592': 'iso-8859-2', + 'iso_8859-2': 'iso-8859-2', + 'iso_8859-2:1987': 'iso-8859-2', + 'l2': 'iso-8859-2', + 'latin2': 'iso-8859-2', + 'csisolatin3': 'iso-8859-3', + 'iso-8859-3': 'iso-8859-3', + 'iso-ir-109': 'iso-8859-3', + 'iso8859-3': 'iso-8859-3', + 'iso88593': 'iso-8859-3', + 'iso_8859-3': 'iso-8859-3', + 'iso_8859-3:1988': 'iso-8859-3', + 'l3': 'iso-8859-3', + 'latin3': 'iso-8859-3', + 'csisolatin4': 'iso-8859-4', + 'iso-8859-4': 'iso-8859-4', + 'iso-ir-110': 'iso-8859-4', + 'iso8859-4': 'iso-8859-4', + 'iso88594': 'iso-8859-4', + 'iso_8859-4': 'iso-8859-4', + 'iso_8859-4:1988': 'iso-8859-4', + 'l4': 'iso-8859-4', + 'latin4': 'iso-8859-4', + 'csisolatincyrillic': 'iso-8859-5', + 'cyrillic': 'iso-8859-5', + 'iso-8859-5': 'iso-8859-5', + 'iso-ir-144': 'iso-8859-5', + 'iso8859-5': 'iso-8859-5', + 'iso88595': 'iso-8859-5', + 'iso_8859-5': 'iso-8859-5', + 'iso_8859-5:1988': 'iso-8859-5', + 'arabic': 'iso-8859-6', + 'asmo-708': 'iso-8859-6', + 'csiso88596e': 'iso-8859-6', + 'csiso88596i': 'iso-8859-6', + 'csisolatinarabic': 'iso-8859-6', + 'ecma-114': 'iso-8859-6', + 'iso-8859-6': 'iso-8859-6', + 'iso-8859-6-e': 'iso-8859-6', + 'iso-8859-6-i': 'iso-8859-6', + 'iso-ir-127': 'iso-8859-6', + 'iso8859-6': 'iso-8859-6', + 'iso88596': 'iso-8859-6', + 'iso_8859-6': 'iso-8859-6', + 'iso_8859-6:1987': 'iso-8859-6', + 'csisolatingreek': 'iso-8859-7', + 'ecma-118': 'iso-8859-7', + 'elot_928': 'iso-8859-7', + 'greek': 'iso-8859-7', + 'greek8': 'iso-8859-7', + 'iso-8859-7': 'iso-8859-7', + 'iso-ir-126': 'iso-8859-7', + 'iso8859-7': 'iso-8859-7', + 'iso88597': 'iso-8859-7', + 'iso_8859-7': 'iso-8859-7', + 'iso_8859-7:1987': 'iso-8859-7', + 'sun_eu_greek': 'iso-8859-7', + 'csiso88598e': 'iso-8859-8', + 'csisolatinhebrew': 'iso-8859-8', + 'hebrew': 'iso-8859-8', + 'iso-8859-8': 'iso-8859-8', + 'iso-8859-8-e': 'iso-8859-8', + 'iso-ir-138': 'iso-8859-8', + 'iso8859-8': 'iso-8859-8', + 'iso88598': 'iso-8859-8', + 'iso_8859-8': 'iso-8859-8', + 'iso_8859-8:1988': 'iso-8859-8', + 'visual': 'iso-8859-8', + 'csiso88598i': 'iso-8859-8-i', + 'iso-8859-8-i': 'iso-8859-8-i', + 'logical': 'iso-8859-8-i', + 'csisolatin6': 'iso-8859-10', + 'iso-8859-10': 'iso-8859-10', + 'iso-ir-157': 'iso-8859-10', + 'iso8859-10': 'iso-8859-10', + 'iso885910': 'iso-8859-10', + 'l6': 'iso-8859-10', + 'latin6': 'iso-8859-10', + 'iso-8859-13': 'iso-8859-13', + 'iso8859-13': 'iso-8859-13', + 'iso885913': 'iso-8859-13', + 'iso-8859-14': 'iso-8859-14', + 'iso8859-14': 'iso-8859-14', + 'iso885914': 'iso-8859-14', + 'csisolatin9': 'iso-8859-15', + 'iso-8859-15': 'iso-8859-15', + 'iso8859-15': 'iso-8859-15', + 'iso885915': 'iso-8859-15', + 'iso_8859-15': 'iso-8859-15', + 'l9': 'iso-8859-15', + 'iso-8859-16': 'iso-8859-16', + 'cskoi8r': 'koi8-r', + 'koi': 'koi8-r', + 'koi8': 'koi8-r', + 'koi8-r': 'koi8-r', + 'koi8_r': 'koi8-r', + 'koi8-u': 'koi8-u', + 'csmacintosh': 'macintosh', + 'mac': 'macintosh', + 'macintosh': 'macintosh', + 'x-mac-roman': 'macintosh', + 'dos-874': 'windows-874', + 'iso-8859-11': 'windows-874', + 'iso8859-11': 'windows-874', + 'iso885911': 'windows-874', + 'tis-620': 'windows-874', + 'windows-874': 'windows-874', + 'cp1250': 'windows-1250', + 'windows-1250': 'windows-1250', + 'x-cp1250': 'windows-1250', + 'cp1251': 'windows-1251', + 'windows-1251': 'windows-1251', + 'x-cp1251': 'windows-1251', + 'ansi_x3.4-1968': 'windows-1252', + 'ascii': 'windows-1252', + 'cp1252': 'windows-1252', + 'cp819': 'windows-1252', + 'csisolatin1': 'windows-1252', + 'ibm819': 'windows-1252', + 'iso-8859-1': 'windows-1252', + 'iso-ir-100': 'windows-1252', + 'iso8859-1': 'windows-1252', + 'iso88591': 'windows-1252', + 'iso_8859-1': 'windows-1252', + 'iso_8859-1:1987': 'windows-1252', + 'l1': 'windows-1252', + 'latin1': 'windows-1252', + 'us-ascii': 'windows-1252', + 'windows-1252': 'windows-1252', + 'x-cp1252': 'windows-1252', + 'cp1253': 'windows-1253', + 'windows-1253': 'windows-1253', + 'x-cp1253': 'windows-1253', + 'cp1254': 'windows-1254', + 'csisolatin5': 'windows-1254', + 'iso-8859-9': 'windows-1254', + 'iso-ir-148': 'windows-1254', + 'iso8859-9': 'windows-1254', + 'iso88599': 'windows-1254', + 'iso_8859-9': 'windows-1254', + 'iso_8859-9:1989': 'windows-1254', + 'l5': 'windows-1254', + 'latin5': 'windows-1254', + 'windows-1254': 'windows-1254', + 'x-cp1254': 'windows-1254', + 'cp1255': 'windows-1255', + 'windows-1255': 'windows-1255', + 'x-cp1255': 'windows-1255', + 'cp1256': 'windows-1256', + 'windows-1256': 'windows-1256', + 'x-cp1256': 'windows-1256', + 'cp1257': 'windows-1257', + 'windows-1257': 'windows-1257', + 'x-cp1257': 'windows-1257', + 'cp1258': 'windows-1258', + 'windows-1258': 'windows-1258', + 'x-cp1258': 'windows-1258', + 'x-mac-cyrillic': 'x-mac-cyrillic', + 'x-mac-ukrainian': 'x-mac-cyrillic', + 'chinese': 'gbk', + 'csgb2312': 'gbk', + 'csiso58gb231280': 'gbk', + 'gb2312': 'gbk', + 'gb_2312': 'gbk', + 'gb_2312-80': 'gbk', + 'gbk': 'gbk', + 'iso-ir-58': 'gbk', + 'x-gbk': 'gbk', + 'gb18030': 'gb18030', + 'hz-gb-2312': 'hz-gb-2312', + 'big5': 'big5', + 'big5-hkscs': 'big5', + 'cn-big5': 'big5', + 'csbig5': 'big5', + 'x-x-big5': 'big5', + 'cseucpkdfmtjapanese': 'euc-jp', + 'euc-jp': 'euc-jp', + 'x-euc-jp': 'euc-jp', + 'csiso2022jp': 'iso-2022-jp', + 'iso-2022-jp': 'iso-2022-jp', + 'csshiftjis': 'shift_jis', + 'ms_kanji': 'shift_jis', + 'shift-jis': 'shift_jis', + 'shift_jis': 'shift_jis', + 'sjis': 'shift_jis', + 'windows-31j': 'shift_jis', + 'x-sjis': 'shift_jis', + 'cseuckr': 'euc-kr', + 'csksc56011987': 'euc-kr', + 'euc-kr': 'euc-kr', + 'iso-ir-149': 'euc-kr', + 'korean': 'euc-kr', + 'ks_c_5601-1987': 'euc-kr', + 'ks_c_5601-1989': 'euc-kr', + 'ksc5601': 'euc-kr', + 'ksc_5601': 'euc-kr', + 'windows-949': 'euc-kr', + 'csiso2022kr': 'iso-2022-kr', + 'iso-2022-kr': 'iso-2022-kr', + 'utf-16be': 'utf-16be', + 'utf-16': 'utf-16le', + 'utf-16le': 'utf-16le', + 'x-user-defined': 'x-user-defined', +} diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/mklabels.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/mklabels.py new file mode 100644 index 0000000..295dc92 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/mklabels.py @@ -0,0 +1,59 @@ +""" + + webencodings.mklabels + ~~~~~~~~~~~~~~~~~~~~~ + + Regenarate the webencodings.labels module. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +import json +try: + from urllib import urlopen +except ImportError: + from urllib.request import urlopen + + +def assert_lower(string): + assert string == string.lower() + return string + + +def generate(url): + parts = ['''\ +""" + + webencodings.labels + ~~~~~~~~~~~~~~~~~~~ + + Map encoding labels to their name. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +# XXX Do not edit! +# This file is automatically generated by mklabels.py + +LABELS = { +'''] + labels = [ + (repr(assert_lower(label)).lstrip('u'), + repr(encoding['name']).lstrip('u')) + for category in json.loads(urlopen(url).read().decode('ascii')) + for encoding in category['encodings'] + for label in encoding['labels']] + max_len = max(len(label) for label, name in labels) + parts.extend( + ' %s:%s %s,\n' % (label, ' ' * (max_len - len(label)), name) + for label, name in labels) + parts.append('}') + return ''.join(parts) + + +if __name__ == '__main__': + print(generate('http://encoding.spec.whatwg.org/encodings.json')) diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/tests.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/tests.py new file mode 100644 index 0000000..e12c10d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/tests.py @@ -0,0 +1,153 @@ +# coding: utf-8 +""" + + webencodings.tests + ~~~~~~~~~~~~~~~~~~ + + A basic test suite for Encoding. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +from . import (lookup, LABELS, decode, encode, iter_decode, iter_encode, + IncrementalDecoder, IncrementalEncoder, UTF8) + + +def assert_raises(exception, function, *args, **kwargs): + try: + function(*args, **kwargs) + except exception: + return + else: # pragma: no cover + raise AssertionError('Did not raise %s.' % exception) + + +def test_labels(): + assert lookup('utf-8').name == 'utf-8' + assert lookup('Utf-8').name == 'utf-8' + assert lookup('UTF-8').name == 'utf-8' + assert lookup('utf8').name == 'utf-8' + assert lookup('utf8').name == 'utf-8' + assert lookup('utf8 ').name == 'utf-8' + assert lookup(' \r\nutf8\t').name == 'utf-8' + assert lookup('u8') is None # Python label. + assert lookup('utf-8 ') is None # Non-ASCII white space. + + assert lookup('US-ASCII').name == 'windows-1252' + assert lookup('iso-8859-1').name == 'windows-1252' + assert lookup('latin1').name == 'windows-1252' + assert lookup('LATIN1').name == 'windows-1252' + assert lookup('latin-1') is None + assert lookup('LATİN1') is None # ASCII-only case insensitivity. + + +def test_all_labels(): + for label in LABELS: + assert decode(b'', label) == ('', lookup(label)) + assert encode('', label) == b'' + for repeat in [0, 1, 12]: + output, _ = iter_decode([b''] * repeat, label) + assert list(output) == [] + assert list(iter_encode([''] * repeat, label)) == [] + decoder = IncrementalDecoder(label) + assert decoder.decode(b'') == '' + assert decoder.decode(b'', final=True) == '' + encoder = IncrementalEncoder(label) + assert encoder.encode('') == b'' + assert encoder.encode('', final=True) == b'' + # All encoding names are valid labels too: + for name in set(LABELS.values()): + assert lookup(name).name == name + + +def test_invalid_label(): + assert_raises(LookupError, decode, b'\xEF\xBB\xBF\xc3\xa9', 'invalid') + assert_raises(LookupError, encode, 'é', 'invalid') + assert_raises(LookupError, iter_decode, [], 'invalid') + assert_raises(LookupError, iter_encode, [], 'invalid') + assert_raises(LookupError, IncrementalDecoder, 'invalid') + assert_raises(LookupError, IncrementalEncoder, 'invalid') + + +def test_decode(): + assert decode(b'\x80', 'latin1') == ('€', lookup('latin1')) + assert decode(b'\x80', lookup('latin1')) == ('€', lookup('latin1')) + assert decode(b'\xc3\xa9', 'utf8') == ('é', lookup('utf8')) + assert decode(b'\xc3\xa9', UTF8) == ('é', lookup('utf8')) + assert decode(b'\xc3\xa9', 'ascii') == ('é', lookup('ascii')) + assert decode(b'\xEF\xBB\xBF\xc3\xa9', 'ascii') == ('é', lookup('utf8')) # UTF-8 with BOM + + assert decode(b'\xFE\xFF\x00\xe9', 'ascii') == ('é', lookup('utf-16be')) # UTF-16-BE with BOM + assert decode(b'\xFF\xFE\xe9\x00', 'ascii') == ('é', lookup('utf-16le')) # UTF-16-LE with BOM + assert decode(b'\xFE\xFF\xe9\x00', 'ascii') == ('\ue900', lookup('utf-16be')) + assert decode(b'\xFF\xFE\x00\xe9', 'ascii') == ('\ue900', lookup('utf-16le')) + + assert decode(b'\x00\xe9', 'UTF-16BE') == ('é', lookup('utf-16be')) + assert decode(b'\xe9\x00', 'UTF-16LE') == ('é', lookup('utf-16le')) + assert decode(b'\xe9\x00', 'UTF-16') == ('é', lookup('utf-16le')) + + assert decode(b'\xe9\x00', 'UTF-16BE') == ('\ue900', lookup('utf-16be')) + assert decode(b'\x00\xe9', 'UTF-16LE') == ('\ue900', lookup('utf-16le')) + assert decode(b'\x00\xe9', 'UTF-16') == ('\ue900', lookup('utf-16le')) + + +def test_encode(): + assert encode('é', 'latin1') == b'\xe9' + assert encode('é', 'utf8') == b'\xc3\xa9' + assert encode('é', 'utf8') == b'\xc3\xa9' + assert encode('é', 'utf-16') == b'\xe9\x00' + assert encode('é', 'utf-16le') == b'\xe9\x00' + assert encode('é', 'utf-16be') == b'\x00\xe9' + + +def test_iter_decode(): + def iter_decode_to_string(input, fallback_encoding): + output, _encoding = iter_decode(input, fallback_encoding) + return ''.join(output) + assert iter_decode_to_string([], 'latin1') == '' + assert iter_decode_to_string([b''], 'latin1') == '' + assert iter_decode_to_string([b'\xe9'], 'latin1') == 'é' + assert iter_decode_to_string([b'hello'], 'latin1') == 'hello' + assert iter_decode_to_string([b'he', b'llo'], 'latin1') == 'hello' + assert iter_decode_to_string([b'hell', b'o'], 'latin1') == 'hello' + assert iter_decode_to_string([b'\xc3\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xEF\xBB\xBF\xc3\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'\xEF\xBB\xBF', b'\xc3', b'\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'\xEF\xBB\xBF', b'a', b'\xc3'], 'latin1') == 'a\uFFFD' + assert iter_decode_to_string([ + b'', b'\xEF', b'', b'', b'\xBB\xBF\xc3', b'\xa9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xEF\xBB\xBF'], 'latin1') == '' + assert iter_decode_to_string([b'\xEF\xBB'], 'latin1') == 'ï»' + assert iter_decode_to_string([b'\xFE\xFF\x00\xe9'], 'latin1') == 'é' + assert iter_decode_to_string([b'\xFF\xFE\xe9\x00'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'', b'\xFF', b'', b'', b'\xFE\xe9', b'\x00'], 'latin1') == 'é' + assert iter_decode_to_string([ + b'', b'h\xe9', b'llo'], 'x-user-defined') == 'h\uF7E9llo' + + +def test_iter_encode(): + assert b''.join(iter_encode([], 'latin1')) == b'' + assert b''.join(iter_encode([''], 'latin1')) == b'' + assert b''.join(iter_encode(['é'], 'latin1')) == b'\xe9' + assert b''.join(iter_encode(['', 'é', '', ''], 'latin1')) == b'\xe9' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16')) == b'\xe9\x00' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16le')) == b'\xe9\x00' + assert b''.join(iter_encode(['', 'é', '', ''], 'utf-16be')) == b'\x00\xe9' + assert b''.join(iter_encode([ + '', 'h\uF7E9', '', 'llo'], 'x-user-defined')) == b'h\xe9llo' + + +def test_x_user_defined(): + encoded = b'2,\x0c\x0b\x1aO\xd9#\xcb\x0f\xc9\xbbt\xcf\xa8\xca' + decoded = '2,\x0c\x0b\x1aO\uf7d9#\uf7cb\x0f\uf7c9\uf7bbt\uf7cf\uf7a8\uf7ca' + encoded = b'aa' + decoded = 'aa' + assert decode(encoded, 'x-user-defined') == (decoded, lookup('x-user-defined')) + assert encode(decoded, 'x-user-defined') == encoded diff --git a/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/x_user_defined.py b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/x_user_defined.py new file mode 100644 index 0000000..d16e326 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-19.0.3-py3.7.egg/pip/_vendor/webencodings/x_user_defined.py @@ -0,0 +1,325 @@ +# coding: utf-8 +""" + + webencodings.x_user_defined + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + An implementation of the x-user-defined encoding. + + :copyright: Copyright 2012 by Simon Sapin + :license: BSD, see LICENSE for details. + +""" + +from __future__ import unicode_literals + +import codecs + + +### Codec APIs + +class Codec(codecs.Codec): + + def encode(self, input, errors='strict'): + return codecs.charmap_encode(input, errors, encoding_table) + + def decode(self, input, errors='strict'): + return codecs.charmap_decode(input, errors, decoding_table) + + +class IncrementalEncoder(codecs.IncrementalEncoder): + def encode(self, input, final=False): + return codecs.charmap_encode(input, self.errors, encoding_table)[0] + + +class IncrementalDecoder(codecs.IncrementalDecoder): + def decode(self, input, final=False): + return codecs.charmap_decode(input, self.errors, decoding_table)[0] + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +### encodings module API + +codec_info = codecs.CodecInfo( + name='x-user-defined', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamreader=StreamReader, + streamwriter=StreamWriter, +) + + +### Decoding Table + +# Python 3: +# for c in range(256): print(' %r' % chr(c if c < 128 else c + 0xF700)) +decoding_table = ( + '\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' + ' ' + '!' + '"' + '#' + '$' + '%' + '&' + "'" + '(' + ')' + '*' + '+' + ',' + '-' + '.' + '/' + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + ':' + ';' + '<' + '=' + '>' + '?' + '@' + 'A' + 'B' + 'C' + 'D' + 'E' + 'F' + 'G' + 'H' + 'I' + 'J' + 'K' + 'L' + 'M' + 'N' + 'O' + 'P' + 'Q' + 'R' + 'S' + 'T' + 'U' + 'V' + 'W' + 'X' + 'Y' + 'Z' + '[' + '\\' + ']' + '^' + '_' + '`' + 'a' + 'b' + 'c' + 'd' + 'e' + 'f' + 'g' + 'h' + 'i' + 'j' + 'k' + 'l' + 'm' + 'n' + 'o' + 'p' + 'q' + 'r' + 's' + 't' + 'u' + 'v' + 'w' + 'x' + 'y' + 'z' + '{' + '|' + '}' + '~' + '\x7f' + '\uf780' + '\uf781' + '\uf782' + '\uf783' + '\uf784' + '\uf785' + '\uf786' + '\uf787' + '\uf788' + '\uf789' + '\uf78a' + '\uf78b' + '\uf78c' + '\uf78d' + '\uf78e' + '\uf78f' + '\uf790' + '\uf791' + '\uf792' + '\uf793' + '\uf794' + '\uf795' + '\uf796' + '\uf797' + '\uf798' + '\uf799' + '\uf79a' + '\uf79b' + '\uf79c' + '\uf79d' + '\uf79e' + '\uf79f' + '\uf7a0' + '\uf7a1' + '\uf7a2' + '\uf7a3' + '\uf7a4' + '\uf7a5' + '\uf7a6' + '\uf7a7' + '\uf7a8' + '\uf7a9' + '\uf7aa' + '\uf7ab' + '\uf7ac' + '\uf7ad' + '\uf7ae' + '\uf7af' + '\uf7b0' + '\uf7b1' + '\uf7b2' + '\uf7b3' + '\uf7b4' + '\uf7b5' + '\uf7b6' + '\uf7b7' + '\uf7b8' + '\uf7b9' + '\uf7ba' + '\uf7bb' + '\uf7bc' + '\uf7bd' + '\uf7be' + '\uf7bf' + '\uf7c0' + '\uf7c1' + '\uf7c2' + '\uf7c3' + '\uf7c4' + '\uf7c5' + '\uf7c6' + '\uf7c7' + '\uf7c8' + '\uf7c9' + '\uf7ca' + '\uf7cb' + '\uf7cc' + '\uf7cd' + '\uf7ce' + '\uf7cf' + '\uf7d0' + '\uf7d1' + '\uf7d2' + '\uf7d3' + '\uf7d4' + '\uf7d5' + '\uf7d6' + '\uf7d7' + '\uf7d8' + '\uf7d9' + '\uf7da' + '\uf7db' + '\uf7dc' + '\uf7dd' + '\uf7de' + '\uf7df' + '\uf7e0' + '\uf7e1' + '\uf7e2' + '\uf7e3' + '\uf7e4' + '\uf7e5' + '\uf7e6' + '\uf7e7' + '\uf7e8' + '\uf7e9' + '\uf7ea' + '\uf7eb' + '\uf7ec' + '\uf7ed' + '\uf7ee' + '\uf7ef' + '\uf7f0' + '\uf7f1' + '\uf7f2' + '\uf7f3' + '\uf7f4' + '\uf7f5' + '\uf7f6' + '\uf7f7' + '\uf7f8' + '\uf7f9' + '\uf7fa' + '\uf7fb' + '\uf7fc' + '\uf7fd' + '\uf7fe' + '\uf7ff' +) + +### Encoding table +encoding_table = codecs.charmap_build(decoding_table) diff --git a/venv/lib/python3.7/site-packages/pykka/__init__.py b/venv/lib/python3.7/site-packages/pykka/__init__.py new file mode 100644 index 0000000..1135b0c --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/__init__.py @@ -0,0 +1,32 @@ +import logging as _logging + +from pykka._exceptions import ActorDeadError, Timeout +from pykka._future import Future, get_all +from pykka._proxy import ActorProxy, CallableProxy, traversable +from pykka._ref import ActorRef +from pykka._registry import ActorRegistry +from pykka._actor import Actor # noqa: Must be imported late +from pykka._threading import ThreadingActor, ThreadingFuture + + +__all__ = [ + 'Actor', + 'ActorDeadError', + 'ActorProxy', + 'ActorRef', + 'ActorRegistry', + 'CallableProxy', + 'Future', + 'ThreadingActor', + 'ThreadingFuture', + 'Timeout', + 'get_all', + 'traversable', +] + + +#: Pykka's :pep:`396` and :pep:`440` compatible version number +__version__ = '2.0.2' + + +_logging.getLogger('pykka').addHandler(_logging.NullHandler()) diff --git a/venv/lib/python3.7/site-packages/pykka/_actor.py b/venv/lib/python3.7/site-packages/pykka/_actor.py new file mode 100644 index 0000000..87c995f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_actor.py @@ -0,0 +1,356 @@ +from __future__ import absolute_import + +import logging +import sys +import threading +import uuid + +from pykka import ActorDeadError, ActorRef, ActorRegistry, messages + + +__all__ = ['Actor'] + +logger = logging.getLogger('pykka') + + +class Actor(object): + """ + To create an actor: + + 1. subclass one of the :class:`Actor` implementations: + + - :class:`~pykka.ThreadingActor` + - :class:`~pykka.gevent.GeventActor` + - :class:`~pykka.eventlet.EventletActor` + + 2. implement your methods, including :meth:`__init__`, as usual, + 3. call :meth:`Actor.start` on your actor class, passing the method any + arguments for your constructor. + + To stop an actor, call :meth:`Actor.stop()` or :meth:`ActorRef.stop()`. + + For example:: + + import pykka + + class MyActor(pykka.ThreadingActor): + def __init__(self, my_arg=None): + super().__init__() + ... # My optional init code with access to start() arguments + + def on_start(self): + ... # My optional setup code in same context as on_receive() + + def on_stop(self): + ... # My optional cleanup code in same context as on_receive() + + def on_failure(self, exception_type, exception_value, traceback): + ... # My optional cleanup code in same context as on_receive() + + def on_receive(self, message): + ... # My optional message handling code for a plain actor + + def a_method(self, ...): + ... # My regular method to be used through an ActorProxy + + my_actor_ref = MyActor.start(my_arg=...) + my_actor_ref.stop() + """ + + @classmethod + def start(cls, *args, **kwargs): + """ + Start an actor and register it in the + :class:`ActorRegistry <pykka.ActorRegistry>`. + + Any arguments passed to :meth:`start` will be passed on to the class + constructor. + + Behind the scenes, the following is happening when you call + :meth:`start`: + + 1. The actor is created: + + 1. :attr:`actor_urn` is initialized with the assigned URN. + + 2. :attr:`actor_inbox` is initialized with a new actor inbox. + + 3. :attr:`actor_ref` is initialized with a :class:`pykka.ActorRef` + object for safely communicating with the actor. + + 4. At this point, your :meth:`__init__()` code can run. + + 2. The actor is registered in :class:`pykka.ActorRegistry`. + + 3. The actor receive loop is started by the actor's associated + thread/greenlet. + + :returns: a :class:`ActorRef` which can be used to access the actor in + a safe manner + """ + obj = cls(*args, **kwargs) + assert obj.actor_ref is not None, ( + 'Actor.__init__() have not been called. ' + 'Did you forget to call super() in your override?' + ) + ActorRegistry.register(obj.actor_ref) + logger.debug('Starting {}'.format(obj)) + obj._start_actor_loop() + return obj.actor_ref + + @staticmethod + def _create_actor_inbox(): + """Internal method for implementors of new actor types.""" + raise NotImplementedError('Use a subclass of Actor') + + @staticmethod + def _create_future(): + """Internal method for implementors of new actor types.""" + raise NotImplementedError('Use a subclass of Actor') + + def _start_actor_loop(self): + """Internal method for implementors of new actor types.""" + raise NotImplementedError('Use a subclass of Actor') + + #: The actor URN string is a universally unique identifier for the actor. + #: It may be used for looking up a specific actor using + #: :meth:`ActorRegistry.get_by_urn`. + actor_urn = None + + #: The actor's inbox. Use :meth:`ActorRef.tell`, :meth:`ActorRef.ask`, and + #: friends to put messages in the inbox. + actor_inbox = None + + #: The actor's :class:`ActorRef` instance. + actor_ref = None + + #: A :class:`threading.Event` representing whether or not the actor should + #: continue processing messages. Use :meth:`stop` to change it. + actor_stopped = None + + def __init__(self, *args, **kwargs): + """ + Your are free to override :meth:`__init__`, but you must call your + superclass' :meth:`__init__` to ensure that fields :attr:`actor_urn`, + :attr:`actor_inbox`, and :attr:`actor_ref` are initialized. + + You can use :func:`super`:: + + super().__init__() + + Or call you superclass directly:: + + pykka.ThreadingActor.__init__(self) + # or + pykka.gevent.GeventActor.__init__(self) + + :meth:`__init__` is called before the actor is started and registered + in :class:`ActorRegistry <pykka.ActorRegistry>`. + """ + self.actor_urn = uuid.uuid4().urn + self.actor_inbox = self._create_actor_inbox() + self.actor_stopped = threading.Event() + + self.actor_ref = ActorRef(self) + + def __str__(self): + return '{} ({})'.format(self.__class__.__name__, self.actor_urn) + + def stop(self): + """ + Stop the actor. + + It's equivalent to calling :meth:`ActorRef.stop` with ``block=False``. + """ + self.actor_ref.tell(messages._ActorStop()) + + def _stop(self): + """ + Stops the actor immediately without processing the rest of the inbox. + """ + ActorRegistry.unregister(self.actor_ref) + self.actor_stopped.set() + logger.debug('Stopped {}'.format(self)) + try: + self.on_stop() + except Exception: + self._handle_failure(*sys.exc_info()) + + def _actor_loop(self): + """ + The actor's event loop. + + This is the method that will be executed by the thread or greenlet. + """ + try: + self.on_start() + except Exception: + self._handle_failure(*sys.exc_info()) + + while not self.actor_stopped.is_set(): + envelope = self.actor_inbox.get() + try: + response = self._handle_receive(envelope.message) + if envelope.reply_to is not None: + envelope.reply_to.set(response) + except Exception: + if envelope.reply_to is not None: + logger.info( + 'Exception returned from {} to caller:'.format(self), + exc_info=sys.exc_info(), + ) + envelope.reply_to.set_exception() + else: + self._handle_failure(*sys.exc_info()) + try: + self.on_failure(*sys.exc_info()) + except Exception: + self._handle_failure(*sys.exc_info()) + except BaseException: + exception_value = sys.exc_info()[1] + logger.debug( + '{!r} in {}. Stopping all actors.'.format( + exception_value, self + ) + ) + self._stop() + ActorRegistry.stop_all() + + while not self.actor_inbox.empty(): + envelope = self.actor_inbox.get() + if envelope.reply_to is not None: + if isinstance(envelope.message, messages._ActorStop): + envelope.reply_to.set(None) + else: + envelope.reply_to.set_exception( + exc_info=( + ActorDeadError, + ActorDeadError( + '{} stopped before handling the message'.format( + self.actor_ref + ) + ), + None, + ) + ) + + def on_start(self): + """ + Hook for doing any setup that should be done *after* the actor is + started, but *before* it starts processing messages. + + For :class:`ThreadingActor`, this method is executed in the actor's own + thread, while :meth:`__init__` is executed in the thread that created + the actor. + + If an exception is raised by this method the stack trace will be + logged, and the actor will stop. + """ + pass + + def on_stop(self): + """ + Hook for doing any cleanup that should be done *after* the actor has + processed the last message, and *before* the actor stops. + + This hook is *not* called when the actor stops because of an unhandled + exception. In that case, the :meth:`on_failure` hook is called instead. + + For :class:`ThreadingActor` this method is executed in the actor's own + thread, immediately before the thread exits. + + If an exception is raised by this method the stack trace will be + logged, and the actor will stop. + """ + pass + + def _handle_failure(self, exception_type, exception_value, traceback): + """Logs unexpected failures, unregisters and stops the actor.""" + logger.error( + 'Unhandled exception in {}:'.format(self), + exc_info=(exception_type, exception_value, traceback), + ) + ActorRegistry.unregister(self.actor_ref) + self.actor_stopped.set() + + def on_failure(self, exception_type, exception_value, traceback): + """ + Hook for doing any cleanup *after* an unhandled exception is raised, + and *before* the actor stops. + + For :class:`ThreadingActor` this method is executed in the actor's own + thread, immediately before the thread exits. + + The method's arguments are the relevant information from + :func:`sys.exc_info`. + + If an exception is raised by this method the stack trace will be + logged, and the actor will stop. + """ + pass + + def _handle_receive(self, message): + """Handles messages sent to the actor.""" + message = messages._upgrade_internal_message(message) + if isinstance(message, messages._ActorStop): + return self._stop() + if isinstance(message, messages.ProxyCall): + callee = self._get_attribute_from_path(message.attr_path) + return callee(*message.args, **message.kwargs) + if isinstance(message, messages.ProxyGetAttr): + attr = self._get_attribute_from_path(message.attr_path) + return attr + if isinstance(message, messages.ProxySetAttr): + parent_attr = self._get_attribute_from_path(message.attr_path[:-1]) + attr_name = message.attr_path[-1] + return setattr(parent_attr, attr_name, message.value) + return self.on_receive(message) + + def on_receive(self, message): + """ + May be implemented for the actor to handle regular non-proxy messages. + + :param message: the message to handle + :type message: any + + :returns: anything that should be sent as a reply to the sender + """ + logger.warning( + 'Unexpected message received by {}: {}'.format(self, message) + ) + + def _get_attribute_from_path(self, attr_path): + """ + Traverses the path and returns the attribute at the end of the path. + """ + attr = self + for attr_name in attr_path: + attr = getattr(attr, attr_name) + return attr + + def _introspect_attribute_from_path(self, attr_path): + """Get attribute information from ``__dict__`` on the container.""" + if not attr_path: + return self + + parent = self._get_attribute_from_path(attr_path[:-1]) + parent_attrs = self._introspect_attributes(parent) + attr_name = attr_path[-1] + + try: + return parent_attrs[attr_name] + except KeyError: + raise AttributeError( + 'type object {!r} has no attribute {!r}'.format( + parent.__class__.__name__, attr_name + ) + ) + + def _introspect_attributes(self, obj): + """Combine ``__dict__`` from ``obj`` and all its superclasses.""" + result = {} + for cls in reversed(obj.__class__.mro()): + result.update(cls.__dict__) + if hasattr(obj, '__dict__'): + result.update(obj.__dict__) + return result diff --git a/venv/lib/python3.7/site-packages/pykka/_compat/__init__.py b/venv/lib/python3.7/site-packages/pykka/_compat/__init__.py new file mode 100644 index 0000000..0f00cf4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_compat/__init__.py @@ -0,0 +1,36 @@ +import sys + + +PY2 = sys.version_info[0] == 2 + +if PY2: + import Queue as queue # noqa + from collections import Callable, Iterable # noqa + + string_types = basestring # noqa + + def reraise(tp, value, tb=None): + exec('raise tp, value, tb') + + await_dunder_future = None + await_keyword = None + +else: + import queue # noqa + from collections.abc import Callable, Iterable # noqa + + string_types = (str,) + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + # `async def` and return inside a generator are syntax errors on Python 2 + # so these must be hidden behind a conditional import. + from pykka._compat.await_py3 import ( # noqa + await_dunder_future, + await_keyword, + ) diff --git a/venv/lib/python3.7/site-packages/pykka/_compat/await_py3.py b/venv/lib/python3.7/site-packages/pykka/_compat/await_py3.py new file mode 100644 index 0000000..73fafe8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_compat/await_py3.py @@ -0,0 +1,8 @@ +def await_dunder_future(self): + yield + value = self.get() + return value + + +async def await_keyword(val): + return await val diff --git a/venv/lib/python3.7/site-packages/pykka/_envelope.py b/venv/lib/python3.7/site-packages/pykka/_envelope.py new file mode 100644 index 0000000..9e19a8e --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_envelope.py @@ -0,0 +1,23 @@ +class Envelope(object): + """ + Envelope to add metadata to a message. + + This is an internal type and is not part of the public API. + + :param message: the message to send + :type message: any + :param reply_to: the future to reply to if there is a response + :type reply_to: :class:`pykka.Future` + """ + + # Using slots speeds up envelope creation with ~20% + __slots__ = ['message', 'reply_to'] + + def __init__(self, message, reply_to=None): + self.message = message + self.reply_to = reply_to + + def __repr__(self): + return 'Envelope(message={!r}, reply_to={!r})'.format( + self.message, self.reply_to + ) diff --git a/venv/lib/python3.7/site-packages/pykka/_exceptions.py b/venv/lib/python3.7/site-packages/pykka/_exceptions.py new file mode 100644 index 0000000..8523ca0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_exceptions.py @@ -0,0 +1,13 @@ +__all__ = ['ActorDeadError', 'Timeout'] + + +class ActorDeadError(Exception): + """Exception raised when trying to use a dead or unavailable actor.""" + + pass + + +class Timeout(Exception): + """Exception raised at future timeout.""" + + pass diff --git a/venv/lib/python3.7/site-packages/pykka/_future.py b/venv/lib/python3.7/site-packages/pykka/_future.py new file mode 100644 index 0000000..50b892d --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_future.py @@ -0,0 +1,264 @@ +import functools + +from pykka import _compat + + +__all__ = ['Future', 'get_all'] + + +class Future(object): + """ + A :class:`Future` is a handle to a value which is available or will be + available in the future. + + Typically returned by calls to actor methods or accesses to actor fields. + + To get hold of the encapsulated value, call :meth:`Future.get` or, if + using Python 3.5+, ``await`` the future. + """ + + def __init__(self): + super(Future, self).__init__() + self._get_hook = None + self._get_hook_result = None + + def get(self, timeout=None): + """ + Get the value encapsulated by the future. + + If the encapsulated value is an exception, it is raised instead of + returned. + + If ``timeout`` is :class:`None`, as default, the method will block + until it gets a reply, potentially forever. If ``timeout`` is an + integer or float, the method will wait for a reply for ``timeout`` + seconds, and then raise :exc:`pykka.Timeout`. + + The encapsulated value can be retrieved multiple times. The future will + only block the first time the value is accessed. + + :param timeout: seconds to wait before timeout + :type timeout: float or :class:`None` + + :raise: :exc:`pykka.Timeout` if timeout is reached + :raise: encapsulated value if it is an exception + :return: encapsulated value if it is not an exception + """ + if self._get_hook is not None: + if self._get_hook_result is None: + self._get_hook_result = self._get_hook(timeout) + return self._get_hook_result + raise NotImplementedError + + def set(self, value=None): + """ + Set the encapsulated value. + + :param value: the encapsulated value or nothing + :type value: any object or :class:`None` + :raise: an exception if set is called multiple times + """ + raise NotImplementedError + + def set_exception(self, exc_info=None): + """ + Set an exception as the encapsulated value. + + You can pass an ``exc_info`` three-tuple, as returned by + :func:`sys.exc_info`. If you don't pass ``exc_info``, + :func:`sys.exc_info` will be called and the value returned by it used. + + In other words, if you're calling :meth:`set_exception`, without any + arguments, from an except block, the exception you're currently + handling will automatically be set on the future. + + :param exc_info: the encapsulated exception + :type exc_info: three-tuple of (exc_class, exc_instance, traceback) + """ + raise NotImplementedError + + def set_get_hook(self, func): + """ + Set a function to be executed when :meth:`get` is called. + + The function will be called when :meth:`get` is called, with the + ``timeout`` value as the only argument. The function's return value + will be returned from :meth:`get`. + + .. versionadded:: 1.2 + + :param func: called to produce return value of :meth:`get` + :type func: function accepting a timeout value + """ + self._get_hook = func + + def filter(self, func): + """ + Return a new future with only the items passing the predicate function. + + If the future's value is an iterable, :meth:`filter` will return a new + future whose value is another iterable with only the items from the + first iterable for which ``func(item)`` is true. If the future's value + isn't an iterable, a :exc:`TypeError` will be raised when :meth:`get` + is called. + + Example:: + + >>> import pykka + >>> f = pykka.ThreadingFuture() + >>> g = f.filter(lambda x: x > 10) + >>> g + <pykka.future.ThreadingFuture at ...> + >>> f.set(range(5, 15)) + >>> f.get() + [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + >>> g.get() + [11, 12, 13, 14] + + .. versionadded:: 1.2 + """ + future = self.__class__() + future.set_get_hook( + lambda timeout: list(filter(func, self.get(timeout))) + ) + return future + + def join(self, *futures): + """ + Return a new future with a list of the result of multiple futures. + + One or more futures can be passed as arguments to :meth:`join`. The new + future returns a list with the results from all the joined futures. + + Example:: + + >>> import pykka + >>> a = pykka.ThreadingFuture() + >>> b = pykka.ThreadingFuture() + >>> c = pykka.ThreadingFuture() + >>> f = a.join(b, c) + >>> a.set('def') + >>> b.set(123) + >>> c.set(False) + >>> f.get() + ['def', 123, False] + + .. versionadded:: 1.2 + """ + future = self.__class__() + future.set_get_hook( + lambda timeout: [f.get(timeout) for f in [self] + list(futures)] + ) + return future + + def map(self, func): + """ + Return a new future with the result of the future passed through a + function. + + Example:: + + >>> import pykka + >>> f = pykka.ThreadingFuture() + >>> g = f.map(lambda x: x + 10) + >>> f.set(30) + >>> g.get() + 40 + + >>> f = pykka.ThreadingFuture() + >>> g = f.map(lambda x: x['foo']) + >>> f.set({'foo': 'bar'}}) + >>> g.get() + 'bar' + + .. versionadded:: 1.2 + + .. versionchanged:: 2.0 + Previously, if the future's result was an iterable (except a + string), the function was applied to each item in the iterable. + This behavior is unpredictable and makes regular use cases like + extracting a single field from a dict difficult, thus the + behavior has been simplified. Now, the entire result value is + passed to the function. + """ + future = self.__class__() + future.set_get_hook(lambda timeout: func(self.get(timeout))) + return future + + def reduce(self, func, *args): + """ + reduce(func[, initial]) + + Return a new future with the result of reducing the future's iterable + into a single value. + + The function of two arguments is applied cumulatively to the items of + the iterable, from left to right. The result of the first function call + is used as the first argument to the second function call, and so on, + until the end of the iterable. If the future's value isn't an iterable, + a :exc:`TypeError` is raised. + + :meth:`reduce` accepts an optional second argument, which will be used + as an initial value in the first function call. If the iterable is + empty, the initial value is returned. + + Example:: + + >>> import pykka + >>> f = pykka.ThreadingFuture() + >>> g = f.reduce(lambda x, y: x + y) + >>> f.set(['a', 'b', 'c']) + >>> g.get() + 'abc' + + >>> f = pykka.ThreadingFuture() + >>> g = f.reduce(lambda x, y: x + y) + >>> f.set([1, 2, 3]) + >>> (1 + 2) + 3 + 6 + >>> g.get() + 6 + + >>> f = pykka.ThreadingFuture() + >>> g = f.reduce(lambda x, y: x + y, 5) + >>> f.set([1, 2, 3]) + >>> ((5 + 1) + 2) + 3 + 11 + >>> g.get() + 11 + + >>> f = pykka.ThreadingFuture() + >>> g = f.reduce(lambda x, y: x + y, 5) + >>> f.set([]) + >>> g.get() + 5 + + .. versionadded:: 1.2 + """ + future = self.__class__() + future.set_get_hook( + lambda timeout: functools.reduce(func, self.get(timeout), *args) + ) + return future + + __await__ = _compat.await_dunder_future + __iter__ = __await__ + + +def get_all(futures, timeout=None): + """ + Collect all values encapsulated in the list of futures. + + If ``timeout`` is not :class:`None`, the method will wait for a reply for + ``timeout`` seconds, and then raise :exc:`pykka.Timeout`. + + :param futures: futures for the results to collect + :type futures: list of :class:`pykka.Future` + + :param timeout: seconds to wait before timeout + :type timeout: float or :class:`None` + + :raise: :exc:`pykka.Timeout` if timeout is reached + :returns: list of results + """ + return [future.get(timeout=timeout) for future in futures] diff --git a/venv/lib/python3.7/site-packages/pykka/_proxy.py b/venv/lib/python3.7/site-packages/pykka/_proxy.py new file mode 100644 index 0000000..843b839 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_proxy.py @@ -0,0 +1,357 @@ +from __future__ import absolute_import + +import logging + +from pykka import ActorDeadError, _compat, messages + + +__all__ = ['ActorProxy'] + +logger = logging.getLogger('pykka') + + +class ActorProxy(object): + """ + An :class:`ActorProxy` wraps an :class:`ActorRef <pykka.ActorRef>` + instance. The proxy allows the referenced actor to be used through regular + method calls and field access. + + You can create an :class:`ActorProxy` from any :class:`ActorRef + <pykka.ActorRef>`:: + + actor_ref = MyActor.start() + actor_proxy = ActorProxy(actor_ref) + + You can also get an :class:`ActorProxy` by using :meth:`proxy() + <pykka.ActorRef.proxy>`:: + + actor_proxy = MyActor.start().proxy() + + **Attributes and method calls** + + When reading an attribute or getting a return value from a method, you get + a :class:`Future <pykka.Future>` object back. To get the enclosed value + from the future, you must call :meth:`get() <pykka.Future.get>` on the + returned future:: + + print(actor_proxy.string_attribute.get()) + print(actor_proxy.count().get() + 1) + + If you call a method just for it's side effects and do not care about the + return value, you do not need to accept the returned future or call + :meth:`get() <pykka.Future.get>` on the future. Simply call the method, and + it will be executed concurrently with your own code:: + + actor_proxy.method_with_side_effect() + + If you want to block your own code from continuing while the other method + is processing, you can use :meth:`get() <pykka.Future.get>` to block until + it completes:: + + actor_proxy.method_with_side_effect().get() + + If you're using Python 3.5+, you can also use the ``await`` keyword to + block until the method completes:: + + await actor_proxy.method_with_side_effect() + + If you access a proxied method as an attribute, without calling it, you + get an :class:`CallableProxy`. + + **Proxy to itself** + + An actor can use a proxy to itself to schedule work for itself. The + scheduled work will only be done after the current message and all messages + already in the inbox are processed. + + For example, if an actor can split a time consuming task into multiple + parts, and after completing each part can ask itself to start on the next + part using proxied calls or messages to itself, it can react faster to + other incoming messages as they will be interleaved with the parts of the + time consuming task. This is especially useful for being able to stop the + actor in the middle of a time consuming task. + + To create a proxy to yourself, use the actor's :attr:`actor_ref + <pykka.Actor.actor_ref>` attribute:: + + proxy_to_myself_in_the_future = self.actor_ref.proxy() + + If you create a proxy in your actor's constructor or :meth:`on_start + <pykka.Actor.on_start>` method, you can create a nice API for deferring + work to yourself in the future:: + + def __init__(self): + ... + self._in_future = self.actor_ref.proxy() + ... + + def do_work(self): + ... + self._in_future.do_more_work() + ... + + def do_more_work(self): + ... + + To avoid infinite loops during proxy introspection, proxies to self + should be kept as private instance attributes by prefixing the attribute + name with ``_``. + + **Examples** + + An example of :class:`ActorProxy` usage: + + .. literalinclude:: ../../examples/counter.py + + :param actor_ref: reference to the actor to proxy + :type actor_ref: :class:`pykka.ActorRef` + + :raise: :exc:`pykka.ActorDeadError` if actor is not available + """ + + #: The actor's :class:`pykka.ActorRef` instance. + actor_ref = None + + def __init__(self, actor_ref, attr_path=None): + if not actor_ref.is_alive(): + raise ActorDeadError('{} not found'.format(actor_ref)) + self.actor_ref = actor_ref + self._actor = actor_ref._actor + self._attr_path = attr_path or tuple() + self._known_attrs = self._introspect_attributes() + self._actor_proxies = {} + self._callable_proxies = {} + + def _introspect_attributes(self): + """Introspects the actor's attributes.""" + result = {} + attr_paths_to_visit = [[attr_name] for attr_name in dir(self._actor)] + + while attr_paths_to_visit: + attr_path = attr_paths_to_visit.pop(0) + + if not self._is_exposable_attribute(attr_path[-1]): + continue + + attr = self._actor._introspect_attribute_from_path(attr_path) + + if self._is_self_proxy(attr): + logger.warning( + ( + '{} attribute {!r} is a proxy to itself. ' + 'Consider making it private by renaming it to {!r}.' + ).format( + self._actor, '.'.join(attr_path), '_' + attr_path[-1] + ) + ) + continue + + traversable = self._is_traversable_attribute(attr) + result[tuple(attr_path)] = { + 'callable': self._is_callable_attribute(attr), + 'traversable': traversable, + } + + if traversable: + for attr_name in dir(attr): + attr_paths_to_visit.append(attr_path + [attr_name]) + + return result + + def _is_exposable_attribute(self, attr_name): + """ + Returns true for any attribute name that may be exposed through + :class:`ActorProxy`. + """ + return not attr_name.startswith('_') + + def _is_self_proxy(self, attr): + """Returns true if attribute is an equivalent actor proxy.""" + return attr == self + + def _is_callable_attribute(self, attr): + """Returns true for any attribute that is callable.""" + return isinstance(attr, _compat.Callable) + + def _is_traversable_attribute(self, attr): + """ + Returns true for any attribute that may be traversed from another + actor through a proxy. + """ + return ( + getattr(attr, '_pykka_traversable', False) is True + or getattr(attr, 'pykka_traversable', False) is True + ) + + def __eq__(self, other): + if not isinstance(other, ActorProxy): + return False + if self._actor != other._actor: + return False + if self._attr_path != other._attr_path: + return False + return True + + def __hash__(self): + return hash((self._actor, self._attr_path)) + + def __repr__(self): + return '<ActorProxy for {}, attr_path={!r}>'.format( + self.actor_ref, self._attr_path + ) + + def __dir__(self): + result = ['__class__'] + result += list(self.__class__.__dict__.keys()) + result += list(self.__dict__.keys()) + result += [attr_path[0] for attr_path in list(self._known_attrs.keys())] + return sorted(result) + + def __getattr__(self, name): + """Get a field or callable from the actor.""" + attr_path = self._attr_path + (name,) + + if attr_path not in self._known_attrs: + self._known_attrs = self._introspect_attributes() + + attr_info = self._known_attrs.get(attr_path) + if attr_info is None: + raise AttributeError('{} has no attribute {!r}'.format(self, name)) + + if attr_info['callable']: + if attr_path not in self._callable_proxies: + self._callable_proxies[attr_path] = CallableProxy( + self.actor_ref, attr_path + ) + return self._callable_proxies[attr_path] + elif attr_info['traversable']: + if attr_path not in self._actor_proxies: + self._actor_proxies[attr_path] = ActorProxy( + self.actor_ref, attr_path + ) + return self._actor_proxies[attr_path] + else: + message = messages.ProxyGetAttr(attr_path=attr_path) + return self.actor_ref.ask(message, block=False) + + def __setattr__(self, name, value): + """ + Set a field on the actor. + + Blocks until the field is set to check if any exceptions was raised. + """ + if name == 'actor_ref' or name.startswith('_'): + return super(ActorProxy, self).__setattr__(name, value) + attr_path = self._attr_path + (name,) + message = messages.ProxySetAttr(attr_path=attr_path, value=value) + return self.actor_ref.ask(message) + + +class CallableProxy(object): + """Proxy to a single method. + + :class:`CallableProxy` instances are returned when accessing methods on a + :class:`ActorProxy` without calling them. + + Example:: + + proxy = AnActor.start().proxy() + + # Ask semantics returns a future. See `__call__()` docs. + future = proxy.do_work() + + # Tell semantics are fire and forget. See `defer()` docs. + proxy.do_work.defer() + """ + + def __init__(self, actor_ref, attr_path): + self.actor_ref = actor_ref + self._attr_path = attr_path + + def __call__(self, *args, **kwargs): + """Call with :meth:`~pykka.ActorRef.ask` semantics. + + Returns a future which will yield the called method's return value. + + If the call raises an exception is set on the future, and will be + reraised by :meth:`~pykka.Future.get`. If the future is left unused, + the exception will not be reraised. Either way, the exception will + also be logged. See :ref:`logging` for details. + """ + message = messages.ProxyCall( + attr_path=self._attr_path, args=args, kwargs=kwargs + ) + return self.actor_ref.ask(message, block=False) + + def defer(self, *args, **kwargs): + """Call with :meth:`~pykka.ActorRef.tell` semantics. + + Does not create or return a future. + + If the call raises an exception, there is no future to set the + exception on. Thus, the actor's :meth:`~pykka.Actor.on_failure` hook + is called instead. + + .. versionadded:: 2.0 + """ + message = messages.ProxyCall( + attr_path=self._attr_path, args=args, kwargs=kwargs + ) + return self.actor_ref.tell(message) + + +def traversable(obj): + """Marks an actor attribute as traversable. + + The traversable marker makes the actor attribute's own methods and + attributes available to users of the actor through an + :class:`~pykka.ActorProxy`. + + Used as a function to mark a single attribute:: + + class AnActor(pykka.ThreadingActor): + playback = pykka.traversable(Playback()) + + class Playback(object): + def play(self): + return True + + This function can also be used as a class decorator, making all instances + of the class traversable:: + + class AnActor(pykka.ThreadingActor): + playback = Playback() + + @pykka.traversable + class Playback(object): + def play(self): + return True + + The third alternative, and the only way in Pykka < 2.0, is to manually + mark a class as traversable by setting the ``pykka_traversable`` attribute + to :class:`True`:: + + class AnActor(pykka.ThreadingActor): + playback = Playback() + + class Playback(object): + pykka_traversable = True + + def play(self): + return True + + When the attribute is marked as traversable, its methods can be executed + in the context of the actor through an actor proxy:: + + proxy = AnActor.start().proxy() + assert proxy.playback.play().get() is True + + .. versionadded:: 2.0 + """ + if hasattr(obj, '__slots__'): + raise Exception( + 'pykka.traversable() cannot be used to mark ' + 'an object using slots as traversable.' + ) + obj._pykka_traversable = True + return obj diff --git a/venv/lib/python3.7/site-packages/pykka/_ref.py b/venv/lib/python3.7/site-packages/pykka/_ref.py new file mode 100644 index 0000000..23d11cf --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_ref.py @@ -0,0 +1,171 @@ +from pykka import ActorDeadError, ActorProxy +from pykka._envelope import Envelope +from pykka.messages import _ActorStop + + +__all__ = ['ActorRef'] + + +class ActorRef(object): + """ + Reference to a running actor which may safely be passed around. + + :class:`ActorRef` instances are returned by :meth:`Actor.start` and the + lookup methods in :class:`ActorRegistry <pykka.ActorRegistry>`. You should + never need to create :class:`ActorRef` instances yourself. + + :param actor: the actor to wrap + :type actor: :class:`Actor` + """ + + #: The class of the referenced actor. + actor_class = None + + #: See :attr:`Actor.actor_urn`. + actor_urn = None + + #: See :attr:`Actor.actor_inbox`. + actor_inbox = None + + #: See :attr:`Actor.actor_stopped`. + actor_stopped = None + + def __init__(self, actor): + self._actor = actor + self.actor_class = actor.__class__ + self.actor_urn = actor.actor_urn + self.actor_inbox = actor.actor_inbox + self.actor_stopped = actor.actor_stopped + + def __repr__(self): + return '<ActorRef for {}>'.format(self) + + def __str__(self): + return '{} ({})'.format(self.actor_class.__name__, self.actor_urn) + + def is_alive(self): + """ + Check if actor is alive. + + This is based on the actor's stopped flag. The actor is not guaranteed + to be alive and responding even though :meth:`is_alive` returns + :class:`True`. + + :return: + Returns :class:`True` if actor is alive, :class:`False` otherwise. + """ + return not self.actor_stopped.is_set() + + def tell(self, message): + """ + Send message to actor without waiting for any response. + + Will generally not block, but if the underlying queue is full it will + block until a free slot is available. + + :param message: message to send + :type message: any + + :raise: :exc:`pykka.ActorDeadError` if actor is not available + :return: nothing + """ + if not self.is_alive(): + raise ActorDeadError('{} not found'.format(self)) + self.actor_inbox.put(Envelope(message)) + + def ask(self, message, block=True, timeout=None): + """ + Send message to actor and wait for the reply. + + The message can be of any type. + If ``block`` is :class:`False`, it will immediately return a + :class:`Future <pykka.Future>` instead of blocking. + + If ``block`` is :class:`True`, and ``timeout`` is :class:`None`, as + default, the method will block until it gets a reply, potentially + forever. If ``timeout`` is an integer or float, the method will wait + for a reply for ``timeout`` seconds, and then raise + :exc:`pykka.Timeout`. + + :param message: message to send + :type message: any + + :param block: whether to block while waiting for a reply + :type block: boolean + + :param timeout: seconds to wait before timeout if blocking + :type timeout: float or :class:`None` + + :raise: :exc:`pykka.Timeout` if timeout is reached if blocking + :raise: any exception returned by the receiving actor if blocking + :return: :class:`pykka.Future`, or response if blocking + """ + future = self.actor_class._create_future() + + try: + if not self.is_alive(): + raise ActorDeadError('{} not found'.format(self)) + except ActorDeadError: + future.set_exception() + else: + self.actor_inbox.put(Envelope(message, reply_to=future)) + + if block: + return future.get(timeout=timeout) + else: + return future + + def stop(self, block=True, timeout=None): + """ + Send a message to the actor, asking it to stop. + + Returns :class:`True` if actor is stopped or was being stopped at the + time of the call. :class:`False` if actor was already dead. If + ``block`` is :class:`False`, it returns a future wrapping the result. + + Messages sent to the actor before the actor is asked to stop will + be processed normally before it stops. + + Messages sent to the actor after the actor is asked to stop will + be replied to with :exc:`pykka.ActorDeadError` after it stops. + + The actor may not be restarted. + + ``block`` and ``timeout`` works as for :meth:`ask`. + + :return: :class:`pykka.Future`, or a boolean result if blocking + """ + ask_future = self.ask(_ActorStop(), block=False) + + def _stop_result_converter(timeout): + try: + ask_future.get(timeout=timeout) + return True + except ActorDeadError: + return False + + converted_future = ask_future.__class__() + converted_future.set_get_hook(_stop_result_converter) + + if block: + return converted_future.get(timeout=timeout) + else: + return converted_future + + def proxy(self): + """ + Wraps the :class:`ActorRef` in an :class:`ActorProxy + <pykka.ActorProxy>`. + + Using this method like this:: + + proxy = AnActor.start().proxy() + + is analogous to:: + + proxy = ActorProxy(AnActor.start()) + + :raise: :exc:`pykka.ActorDeadError` if actor is not available + :return: :class:`pykka.ActorProxy` + """ + return ActorProxy(self) diff --git a/venv/lib/python3.7/site-packages/pykka/_registry.py b/venv/lib/python3.7/site-packages/pykka/_registry.py new file mode 100644 index 0000000..1ee682b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_registry.py @@ -0,0 +1,171 @@ +from __future__ import absolute_import + +import logging +import threading + +from pykka import _compat + +logger = logging.getLogger('pykka') + + +__all__ = ['ActorRegistry'] + + +class ActorRegistry(object): + """ + Registry which provides easy access to all running actors. + + Contains global state, but should be thread-safe. + """ + + _actor_refs = [] + _actor_refs_lock = threading.RLock() + + @classmethod + def broadcast(cls, message, target_class=None): + """ + Broadcast ``message`` to all actors of the specified ``target_class``. + + If no ``target_class`` is specified, the message is broadcasted to all + actors. + + :param message: the message to send + :type message: any + + :param target_class: optional actor class to broadcast the message to + :type target_class: class or class name + """ + if isinstance(target_class, _compat.string_types): + targets = cls.get_by_class_name(target_class) + elif target_class is not None: + targets = cls.get_by_class(target_class) + else: + targets = cls.get_all() + for ref in targets: + ref.tell(message) + + @classmethod + def get_all(cls): + """ + Get :class:`ActorRef <pykka.ActorRef>` for all running actors. + + :returns: list of :class:`pykka.ActorRef` + """ + with cls._actor_refs_lock: + return cls._actor_refs[:] + + @classmethod + def get_by_class(cls, actor_class): + """ + Get :class:`ActorRef` for all running actors of the given class, or of + any subclass of the given class. + + :param actor_class: actor class, or any superclass of the actor + :type actor_class: class + + :returns: list of :class:`pykka.ActorRef` + """ + with cls._actor_refs_lock: + return [ + ref + for ref in cls._actor_refs + if issubclass(ref.actor_class, actor_class) + ] + + @classmethod + def get_by_class_name(cls, actor_class_name): + """ + Get :class:`ActorRef` for all running actors of the given class + name. + + :param actor_class_name: actor class name + :type actor_class_name: string + + :returns: list of :class:`pykka.ActorRef` + """ + with cls._actor_refs_lock: + return [ + ref + for ref in cls._actor_refs + if ref.actor_class.__name__ == actor_class_name + ] + + @classmethod + def get_by_urn(cls, actor_urn): + """ + Get an actor by its universally unique URN. + + :param actor_urn: actor URN + :type actor_urn: string + + :returns: :class:`pykka.ActorRef` or :class:`None` if not found + """ + with cls._actor_refs_lock: + refs = [ + ref for ref in cls._actor_refs if ref.actor_urn == actor_urn + ] + if refs: + return refs[0] + + @classmethod + def register(cls, actor_ref): + """ + Register an :class:`ActorRef` in the registry. + + This is done automatically when an actor is started, e.g. by calling + :meth:`Actor.start() <pykka.Actor.start>`. + + :param actor_ref: reference to the actor to register + :type actor_ref: :class:`pykka.ActorRef` + """ + with cls._actor_refs_lock: + cls._actor_refs.append(actor_ref) + logger.debug('Registered {}'.format(actor_ref)) + + @classmethod + def stop_all(cls, block=True, timeout=None): + """ + Stop all running actors. + + ``block`` and ``timeout`` works as for + :meth:`ActorRef.stop() <pykka.ActorRef.stop>`. + + If ``block`` is :class:`True`, the actors are guaranteed to be stopped + in the reverse of the order they were started in. This is helpful if + you have simple dependencies in between your actors, where it is + sufficient to shut down actors in a LIFO manner: last started, first + stopped. + + If you have more complex dependencies in between your actors, you + should take care to shut them down in the required order yourself, e.g. + by stopping dependees from a dependency's + :meth:`on_stop() <pykka.Actor.on_stop>` method. + + :returns: If not blocking, a list with a future for each stop action. + If blocking, a list of return values from + :meth:`pykka.ActorRef.stop`. + """ + return [ref.stop(block, timeout) for ref in reversed(cls.get_all())] + + @classmethod + def unregister(cls, actor_ref): + """ + Remove an :class:`ActorRef <pykka.ActorRef>` from the registry. + + This is done automatically when an actor is stopped, e.g. by calling + :meth:`Actor.stop() <pykka.Actor.stop>`. + + :param actor_ref: reference to the actor to unregister + :type actor_ref: :class:`pykka.ActorRef` + """ + removed = False + with cls._actor_refs_lock: + if actor_ref in cls._actor_refs: + cls._actor_refs.remove(actor_ref) + removed = True + if removed: + logger.debug('Unregistered {}'.format(actor_ref)) + else: + logger.debug( + 'Unregistered {} (not found in registry)'.format(actor_ref) + ) diff --git a/venv/lib/python3.7/site-packages/pykka/_threading.py b/venv/lib/python3.7/site-packages/pykka/_threading.py new file mode 100644 index 0000000..57faf85 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/_threading.py @@ -0,0 +1,97 @@ +from __future__ import absolute_import + +import sys +import threading + +from pykka import Actor, Future, Timeout, _compat + + +__all__ = ['ThreadingActor', 'ThreadingFuture'] + + +class ThreadingFuture(Future): + """ + :class:`ThreadingFuture` implements :class:`Future` for use with + :class:`ThreadingActor <pykka.ThreadingActor>`. + + The future is implemented using a :class:`queue.Queue`. + + The future does *not* make a copy of the object which is :meth:`set() + <pykka.Future.set>` on it. It is the setters responsibility to only pass + immutable objects or make a copy of the object before setting it on the + future. + + .. versionchanged:: 0.14 + Previously, the encapsulated value was a copy made with + :func:`copy.deepcopy`, unless the encapsulated value was a future, in + which case the original future was encapsulated. + """ + + def __init__(self): + super(ThreadingFuture, self).__init__() + self._queue = _compat.queue.Queue(maxsize=1) + self._data = None + + def get(self, timeout=None): + try: + return super(ThreadingFuture, self).get(timeout=timeout) + except NotImplementedError: + pass + + try: + if self._data is None: + self._data = self._queue.get(True, timeout) + if 'exc_info' in self._data: + _compat.reraise(*self._data['exc_info']) + else: + return self._data['value'] + except _compat.queue.Empty: + raise Timeout('{} seconds'.format(timeout)) + + def set(self, value=None): + self._queue.put({'value': value}, block=False) + + def set_exception(self, exc_info=None): + assert exc_info is None or len(exc_info) == 3 + self._queue.put({'exc_info': exc_info or sys.exc_info()}) + + +class ThreadingActor(Actor): + """ + :class:`ThreadingActor` implements :class:`Actor` using regular Python + threads. + + This implementation is slower than :class:`GeventActor + <pykka.gevent.GeventActor>`, but can be used in a process with other + threads that are not Pykka actors. + """ + + use_daemon_thread = False + """ + A boolean value indicating whether this actor is executed on a thread that + is a daemon thread (:class:`True`) or not (:class:`False`). This must be + set before :meth:`pykka.Actor.start` is called, otherwise + :exc:`RuntimeError` is raised. + + The entire Python program exits when no alive non-daemon threads are left. + This means that an actor running on a daemon thread may be interrupted at + any time, and there is no guarantee that cleanup will be done or that + :meth:`pykka.Actor.on_stop` will be called. + + Actors do not inherit the daemon flag from the actor that made it. It + always has to be set explicitly for the actor to run on a daemonic thread. + """ + + @staticmethod + def _create_actor_inbox(): + return _compat.queue.Queue() + + @staticmethod + def _create_future(): + return ThreadingFuture() + + def _start_actor_loop(self): + thread = threading.Thread(target=self._actor_loop) + thread.name = thread.name.replace('Thread', self.__class__.__name__) + thread.daemon = self.use_daemon_thread + thread.start() diff --git a/venv/lib/python3.7/site-packages/pykka/debug.py b/venv/lib/python3.7/site-packages/pykka/debug.py new file mode 100644 index 0000000..f62556f --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/debug.py @@ -0,0 +1,67 @@ +from __future__ import absolute_import + +import logging +import sys +import threading +import traceback + + +logger = logging.getLogger('pykka') + + +__all__ = ['log_thread_tracebacks'] + + +def log_thread_tracebacks(*args, **kwargs): + """Logs at :attr:`logging.CRITICAL` level a traceback for each running + thread. + + This can be a convenient tool for debugging deadlocks. + + The function accepts any arguments so that it can easily be used as e.g. a + signal handler, but it does not use the arguments for anything. + + To use this function as a signal handler, setup logging with a + :attr:`logging.CRITICAL` threshold or lower and make your main thread + register this with the :mod:`signal` module:: + + import logging + import signal + + import pykka.debug + + logging.basicConfig(level=logging.DEBUG) + signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks) + + If your application deadlocks, send the `SIGUSR1` signal to the process:: + + kill -SIGUSR1 <pid of your process> + + Signal handler caveats: + + - The function *must* be registered as a signal handler by your main + thread. If not, :func:`signal.signal` will raise a :exc:`ValueError`. + + - All signals in Python are handled by the main thread. Thus, the signal + will only be handled, and the tracebacks logged, if your main thread is + available to do some work. Making your main thread idle using + :func:`time.sleep` is OK. The signal will awaken your main thread. + Blocking your main thread on e.g. :func:`queue.Queue.get` or + :meth:`pykka.Future.get` will break signal handling, and thus you won't + be able to signal your process to print the thread tracebacks. + + The morale is: setup signals using your main thread, start your actors, + then let your main thread relax for the rest of your application's life + cycle. + + .. versionadded:: 1.1 + """ + + thread_names = dict((t.ident, t.name) for t in threading.enumerate()) + + for ident, frame in sys._current_frames().items(): + name = thread_names.get(ident, '?') + stack = ''.join(traceback.format_stack(frame)) + logger.critical( + 'Current state of {} (ident: {}):\n{}'.format(name, ident, stack) + ) diff --git a/venv/lib/python3.7/site-packages/pykka/eventlet.py b/venv/lib/python3.7/site-packages/pykka/eventlet.py new file mode 100644 index 0000000..9901853 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/eventlet.py @@ -0,0 +1,107 @@ +from __future__ import absolute_import + +import sys + +import eventlet +import eventlet.event +import eventlet.queue + +from pykka import Actor, Future, Timeout + + +__all__ = ['EventletActor', 'EventletEvent', 'EventletFuture'] + + +class EventletEvent(eventlet.event.Event): + """ + :class:`EventletEvent` adapts :class:`eventlet.event.Event` to + :class:`threading.Event` interface. + """ + + def set(self): + if self.ready(): + self.reset() + self.send() + + def is_set(self): + return self.ready() + + isSet = is_set + + def clear(self): + if self.ready(): + self.reset() + + def wait(self, timeout): + if timeout is not None: + wait_timeout = eventlet.Timeout(timeout) + + try: + with wait_timeout: + super(EventletEvent, self).wait() + except eventlet.Timeout as t: + if t is not wait_timeout: + raise + return False + else: + self.event.wait() + + return True + + +class EventletFuture(Future): + """ + :class:`EventletFuture` implements :class:`pykka.Future` for use with + :class:`EventletActor`. + """ + + event = None + + def __init__(self): + super(EventletFuture, self).__init__() + self.event = eventlet.event.Event() + + def get(self, timeout=None): + try: + return super(EventletFuture, self).get(timeout=timeout) + except NotImplementedError: + pass + + if timeout is not None: + wait_timeout = eventlet.Timeout(timeout) + try: + with wait_timeout: + return self.event.wait() + except eventlet.Timeout as t: + if t is not wait_timeout: + raise + raise Timeout(t) + else: + return self.event.wait() + + def set(self, value=None): + self.event.send(value) + + def set_exception(self, exc_info=None): + assert exc_info is None or len(exc_info) == 3 + self.event.send_exception(*(exc_info or sys.exc_info())) + + +class EventletActor(Actor): + """ + :class:`EventletActor` implements :class:`pykka.Actor` using the `eventlet + <https://eventlet.net/>`_ library. + + This implementation uses eventlet green threads. + """ + + @staticmethod + def _create_actor_inbox(): + return eventlet.queue.Queue() + + @staticmethod + def _create_future(): + return EventletFuture() + + def _start_actor_loop(self): + eventlet.greenthread.spawn(self._actor_loop) diff --git a/venv/lib/python3.7/site-packages/pykka/gevent.py b/venv/lib/python3.7/site-packages/pykka/gevent.py new file mode 100644 index 0000000..9fa0781 --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/gevent.py @@ -0,0 +1,73 @@ +from __future__ import absolute_import + +import sys + +import gevent +import gevent.event +import gevent.queue + +from pykka import Actor, Future, Timeout + + +__all__ = ['GeventActor', 'GeventFuture'] + + +class GeventFuture(Future): + """ + :class:`GeventFuture` implements :class:`pykka.Future` for use with + :class:`GeventActor`. + + It encapsulates a :class:`gevent.event.AsyncResult` object which may be + used directly, though it will couple your code with gevent. + """ + + #: The encapsulated :class:`gevent.event.AsyncResult` + async_result = None + + def __init__(self, async_result=None): + super(GeventFuture, self).__init__() + if async_result is None: + async_result = gevent.event.AsyncResult() + self.async_result = async_result + + def get(self, timeout=None): + try: + return super(GeventFuture, self).get(timeout=timeout) + except NotImplementedError: + pass + + try: + return self.async_result.get(timeout=timeout) + except gevent.Timeout as e: + raise Timeout(e) + + def set(self, value=None): + assert not self.async_result.ready(), 'value has already been set' + self.async_result.set(value) + + def set_exception(self, exc_info=None): + assert exc_info is None or len(exc_info) == 3 + exc_info = exc_info or sys.exc_info() + self.async_result.set_exception(exc_info[1], exc_info=exc_info) + + +class GeventActor(Actor): + """ + :class:`GeventActor` implements :class:`pykka.Actor` using the `gevent + <http://www.gevent.org/>`_ library. gevent is a coroutine-based Python + networking library that uses greenlet to provide a high-level synchronous + API on top of libevent event loop. + + This is a very fast implementation. + """ + + @staticmethod + def _create_actor_inbox(): + return gevent.queue.Queue() + + @staticmethod + def _create_future(): + return GeventFuture() + + def _start_actor_loop(self): + gevent.Greenlet.spawn(self._actor_loop) diff --git a/venv/lib/python3.7/site-packages/pykka/messages.py b/venv/lib/python3.7/site-packages/pykka/messages.py new file mode 100644 index 0000000..8c5742b --- /dev/null +++ b/venv/lib/python3.7/site-packages/pykka/messages.py @@ -0,0 +1,77 @@ +""" +The :mod:`pykka.messages` module contains Pykka's own actor messages. + +In general, you should not need to use any of these classes. However, they have +been made part of the public API so that certain optimizations can be done +without touching Pykka's internals. + +An example is to combine :meth:`~pykka.ActorRef.ask` and :class:`ProxyCall` +to call a method on an actor without having to spend any resources on creating +a proxy object:: + + reply = actor_ref.ask( + ProxyCall( + attr_path=['my_method'], + args=['foo'], + kwargs={'bar': 'baz'} + ) + ) + +Another example is to use :meth:`~pykka.ActorRef.tell` instead of +:meth:`~pykka.ActorRef.ask` for the proxy method call, and thus avoid the +creation of a future for the return value if you don't need it. + +It should be noted that these optimizations should only be necessary in very +special circumstances. +""" + +import warnings +from collections import namedtuple + + +# Internal actor messages +_ActorStop = namedtuple('ActorStop', []) + +# Public proxy messages +# TODO Add docstrings to classes and attributes once we drop Python 2.7 support +ProxyCall = namedtuple('ProxyCall', ['attr_path', 'args', 'kwargs']) +ProxyGetAttr = namedtuple('ProxyGetAttr', ['attr_path']) +ProxySetAttr = namedtuple('ProxySetAttr', ['attr_path', 'value']) + + +def _upgrade_internal_message(message): + """Filter that upgrades dict-based internal messages to the new format. + + This is needed for a transitional period because Mopidy < 3 uses + the old internal message format directly, and maybe others. + """ + + if not isinstance(message, dict): + return message + if not message.get('command', '').startswith('pykka_'): + return message + + warnings.warn( + 'Pykka received a dict-based internal message. ' + 'This is deprecated and will be unsupported in the future. ' + 'Message: {!r}'.format(message), + DeprecationWarning, + ) + + command = message.get('command') + if command == 'pykka_stop': + return _ActorStop() + elif command == 'pykka_call': + return ProxyCall( + attr_path=message['attr_path'], + args=message['args'], + kwargs=message['kwargs'], + ) + elif command == 'pykka_getattr': + return ProxyGetAttr(attr_path=message['attr_path']) + elif command == 'pykka_setattr': + return ProxySetAttr( + attr_path=message['attr_path'], value=message['value'] + ) + else: + raise ValueError('Unknown internal message: {!r}'.format(message)) diff --git a/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE new file mode 100644 index 0000000..841c602 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE @@ -0,0 +1,13 @@ +Copyright 2018 Kenneth Reitz + + 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 + + https://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. diff --git a/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA new file mode 100644 index 0000000..02f2eca --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA @@ -0,0 +1,145 @@ +Metadata-Version: 2.1 +Name: requests +Version: 2.22.0 +Summary: Python HTTP for Humans. +Home-page: http://python-requests.org +Author: Kenneth Reitz +Author-email: me@kennethreitz.org +License: Apache 2.0 +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Description-Content-Type: text/markdown +Requires-Dist: chardet (<3.1.0,>=3.0.2) +Requires-Dist: idna (<2.9,>=2.5) +Requires-Dist: urllib3 (!=1.25.0,!=1.25.1,<1.26,>=1.21.1) +Requires-Dist: certifi (>=2017.4.17) +Provides-Extra: security +Requires-Dist: pyOpenSSL (>=0.14) ; extra == 'security' +Requires-Dist: cryptography (>=1.3.4) ; extra == 'security' +Requires-Dist: idna (>=2.0.0) ; extra == 'security' +Provides-Extra: socks +Requires-Dist: PySocks (!=1.5.7,>=1.5.6) ; extra == 'socks' +Requires-Dist: win-inet-pton ; (sys_platform == "win32" and python_version == "2.7") and extra == 'socks' + +Requests: HTTP for Humans™ +========================== + +[![image](https://img.shields.io/pypi/v/requests.svg)](https://pypi.org/project/requests/) +[![image](https://img.shields.io/pypi/l/requests.svg)](https://pypi.org/project/requests/) +[![image](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests/) +[![codecov.io](https://codecov.io/github/requests/requests/coverage.svg?branch=master)](https://codecov.io/github/requests/requests) +[![image](https://img.shields.io/github/contributors/requests/requests.svg)](https://github.com/requests/requests/graphs/contributors) +[![image](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/kennethreitz) + +Requests is the only *Non-GMO* HTTP library for Python, safe for human +consumption. + +![image](https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg) + +Behold, the power of Requests: + +``` {.sourceCode .python} +>>> import requests +>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) +>>> r.status_code +200 +>>> r.headers['content-type'] +'application/json; charset=utf8' +>>> r.encoding +'utf-8' +>>> r.text +u'{"type":"User"...' +>>> r.json() +{u'disk_usage': 368627, u'private_gists': 484, ...} +``` + +See [the similar code, sans Requests](https://gist.github.com/973705). + +[![image](https://raw.githubusercontent.com/requests/requests/master/docs/_static/requests-logo-small.png)](http://docs.python-requests.org/) + +Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, +without the need for manual labor. There's no need to manually add query +strings to your URLs, or to form-encode your POST data. Keep-alive and +HTTP connection pooling are 100% automatic, thanks to +[urllib3](https://github.com/shazow/urllib3). + +Besides, all the cool kids are doing it. Requests is one of the most +downloaded Python packages of all time, pulling in over 11,000,000 +downloads every month. You don't want to be left out! + +Feature Support +--------------- + +Requests is ready for today's web. + +- International Domains and URLs +- Keep-Alive & Connection Pooling +- Sessions with Cookie Persistence +- Browser-style SSL Verification +- Basic/Digest Authentication +- Elegant Key/Value Cookies +- Automatic Decompression +- Automatic Content Decoding +- Unicode Response Bodies +- Multipart File Uploads +- HTTP(S) Proxy Support +- Connection Timeouts +- Streaming Downloads +- `.netrc` Support +- Chunked Requests + +Requests officially supports Python 2.7 & 3.4–3.7, and runs great on +PyPy. + +Installation +------------ + +To install Requests, simply use [pipenv](http://pipenv.org/) (or pip, of +course): + +``` {.sourceCode .bash} +$ pipenv install requests +✨🍰✨ +``` + +Satisfaction guaranteed. + +Documentation +------------- + +Fantastic documentation is available at +<http://docs.python-requests.org/>, for a limited time only. + +How to Contribute +----------------- + +1. Become more familiar with the project by reading our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/) and our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/). +2. Check for open issues or open a fresh issue to start a discussion + around a feature idea or a bug. There is a [Contributor + Friendly](https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open) + tag for issues that should be ideal for people who are not very + familiar with the codebase yet. +3. Fork [the repository](https://github.com/requests/requests) on + GitHub to start making your changes to the **master** branch (or + branch off of it). +4. Write a test which shows that the bug was fixed or that the feature + works as expected. +5. Send a pull request and bug the maintainer until it gets merged and + published. :) Make sure to add yourself to + [AUTHORS](https://github.com/requests/requests/blob/master/AUTHORS.rst). + + + diff --git a/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/RECORD b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/RECORD new file mode 100644 index 0000000..aacacfc --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/RECORD @@ -0,0 +1,42 @@ +requests-2.22.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +requests-2.22.0.dist-info/LICENSE,sha256=vkGrrCxA-FMDB-jRcsWQtHb0pIi8amj43le3z2R4Zoc,582 +requests-2.22.0.dist-info/METADATA,sha256=sJ1ZdIgF0uoV9U58VVoEZv1QTyMCpmc2MQnbkob3nsE,5523 +requests-2.22.0.dist-info/RECORD,, +requests-2.22.0.dist-info/WHEEL,sha256=h_aVn5OB2IERUjMbi2pucmR_zzWJtk303YXvhh60NJ8,110 +requests-2.22.0.dist-info/top_level.txt,sha256=fMSVmHfb5rbGOo6xv-O_tUX6j-WyixssE-SnwcDRxNQ,9 +requests/__init__.py,sha256=PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA,3921 +requests/__pycache__/__init__.cpython-37.pyc,, +requests/__pycache__/__version__.cpython-37.pyc,, +requests/__pycache__/_internal_utils.cpython-37.pyc,, +requests/__pycache__/adapters.cpython-37.pyc,, +requests/__pycache__/api.cpython-37.pyc,, +requests/__pycache__/auth.cpython-37.pyc,, +requests/__pycache__/certs.cpython-37.pyc,, +requests/__pycache__/compat.cpython-37.pyc,, +requests/__pycache__/cookies.cpython-37.pyc,, +requests/__pycache__/exceptions.cpython-37.pyc,, +requests/__pycache__/help.cpython-37.pyc,, +requests/__pycache__/hooks.cpython-37.pyc,, +requests/__pycache__/models.cpython-37.pyc,, +requests/__pycache__/packages.cpython-37.pyc,, +requests/__pycache__/sessions.cpython-37.pyc,, +requests/__pycache__/status_codes.cpython-37.pyc,, +requests/__pycache__/structures.cpython-37.pyc,, +requests/__pycache__/utils.cpython-37.pyc,, +requests/__version__.py,sha256=Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc,436 +requests/_internal_utils.py,sha256=Zx3PnEUccyfsB-ie11nZVAW8qClJy0gx1qNME7rgT18,1096 +requests/adapters.py,sha256=WelSM1BCQXdbjEuDsBxqKDADeY8BHmxlrwbNnLN2rr4,21344 +requests/api.py,sha256=fbUo11QoLOoNgWU6FfvNz8vMj9bE_cMmICXBa7TZHJs,6271 +requests/auth.py,sha256=QB2-cSUj1jrvWZfPXttsZpyAacQgtKLVk14vQW9TpSE,10206 +requests/certs.py,sha256=dOB5rV2DZ13dEhq9BUa_4hd5kAqg59e_zUZB00faYz8,453 +requests/compat.py,sha256=FVIeTOniQMHQkeE2JdJvar3OZ-b4IFh8aNezIn45zws,1678 +requests/cookies.py,sha256=Y-bKX6TvW3FnYlE6Au0SXtVVWcaNdFvuAwQxw-G0iTI,18430 +requests/exceptions.py,sha256=Q8YeWWxiHHXhkEynLpMgC_6_r_ZTYw2aITs9wCSAZNY,3185 +requests/help.py,sha256=lLcBtKAar8T6T78e9Tc4Zfd_EEJFhntxgib1JHNctEI,3515 +requests/hooks.py,sha256=QReGyy0bRcr5rkwCuObNakbYsc7EkiKeBwG4qHekr2Q,757 +requests/models.py,sha256=bce6oORR26SY-dVPaqMpdBunD1zXzrgMSlH6jhfvuRA,34210 +requests/packages.py,sha256=Q2rF0L5mc3wQAvc6q_lAVtPTDOaOeFgD-7kWSQLkjEQ,542 +requests/sessions.py,sha256=DjbCotDW6xSAaBsjbW-L8l4N0UcwmrxVNgSrZgIjGWM,29332 +requests/status_codes.py,sha256=XWlcpBjbCtq9sSqpH9_KKxgnLTf9Z__wCWolq21ySlg,4129 +requests/structures.py,sha256=zoP8qly2Jak5e89HwpqjN1z2diztI-_gaqts1raJJBc,2981 +requests/utils.py,sha256=LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A,30049 diff --git a/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL new file mode 100644 index 0000000..78e6f69 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.4) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt @@ -0,0 +1 @@ +requests diff --git a/venv/lib/python3.7/site-packages/requests/__init__.py b/venv/lib/python3.7/site-packages/requests/__init__.py new file mode 100644 index 0000000..9a899df --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/__init__.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- + +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. Basic GET +usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> 'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key2": "value2", + "key1": "value1" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at <http://python-requests.org>. + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import urllib3 +import chardet +import warnings +from .exceptions import RequestsDependencyWarning + + +def check_compatibility(urllib3_version, chardet_version): + 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') + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, <= 1.25 + assert major == 1 + assert minor >= 21 + assert minor <= 25 + + # Check chardet for compatibility. + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet >= 3.0.2, < 3.1.0 + assert major == 3 + assert minor < 1 + assert patch >= 2 + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + 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) + warnings.warn(warning, RequestsDependencyWarning) + +# Check imported dependencies for compatibility. +try: + check_compatibility(urllib3.__version__, chardet.__version__) +except (AssertionError, ValueError): + warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet.__version__), + RequestsDependencyWarning) + +# Attempt to enable urllib3's SNI support, if possible +try: + from 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 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 +) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/venv/lib/python3.7/site-packages/requests/__version__.py b/venv/lib/python3.7/site-packages/requests/__version__.py new file mode 100644 index 0000000..9844f74 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = 'requests' +__description__ = 'Python HTTP for Humans.' +__url__ = 'http://python-requests.org' +__version__ = '2.22.0' +__build__ = 0x022200 +__author__ = 'Kenneth Reitz' +__author_email__ = 'me@kennethreitz.org' +__license__ = 'Apache 2.0' +__copyright__ = 'Copyright 2019 Kenneth Reitz' +__cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/venv/lib/python3.7/site-packages/requests/_internal_utils.py b/venv/lib/python3.7/site-packages/requests/_internal_utils.py new file mode 100644 index 0000000..759d9a5 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/_internal_utils.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +from .compat import is_py2, builtin_str, str + + +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. + """ + if isinstance(string, builtin_str): + out = string + else: + if is_py2: + out = string.encode(encoding) + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode('ascii') + return True + except UnicodeEncodeError: + return False diff --git a/venv/lib/python3.7/site-packages/requests/adapters.py b/venv/lib/python3.7/site-packages/requests/adapters.py new file mode 100644 index 0000000..fa4d9b3 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/adapters.py @@ -0,0 +1,533 @@ +# -*- coding: utf-8 -*- + +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket + +from urllib3.poolmanager import PoolManager, proxy_from_url +from urllib3.response import HTTPResponse +from urllib3.util import parse_url +from urllib3.util import Timeout as TimeoutSauce +from urllib3.util.retry import Retry +from urllib3.exceptions import ClosedPoolError +from urllib3.exceptions import ConnectTimeoutError +from urllib3.exceptions import HTTPError as _HTTPError +from urllib3.exceptions import MaxRetryError +from urllib3.exceptions import NewConnectionError +from urllib3.exceptions import ProxyError as _ProxyError +from urllib3.exceptions import ProtocolError +from urllib3.exceptions import ReadTimeoutError +from urllib3.exceptions import SSLError as _SSLError +from urllib3.exceptions import ResponseError +from urllib3.exceptions import LocationValueError + +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 + +try: + from 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): + """The Base Transport Adapter""" + + def __init__(self): + super(BaseAdapter, self).__init__() + + 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. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session <Session>` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> 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): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block) + + 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 + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + 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. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith('socks'): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith('https') and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + 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)) + + 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.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + 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)) + 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)) + + def build_response(self, req, resp): + """Builds a :class:`Response <requests.Response>` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>` + + :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, 'status', None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode('utf-8') + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + 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.") + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + 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') + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers['Proxy-Authorization'] = _basic_auth_str(username, + password) + + return headers + + 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. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + 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) + + 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) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout + ) + + # Send the request. + else: + 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) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + 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(i) + 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() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False + ) + except: + # If we hit any problems here, clean up the connection. + # Then, reraise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/venv/lib/python3.7/site-packages/requests/api.py b/venv/lib/python3.7/site-packages/requests/api.py new file mode 100644 index 0000000..ef71d07 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/api.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request <Request>`. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response <Response>` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + <Response [200]> + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('get', url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('options', url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return request('head', url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('post', url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('put', url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('patch', url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('delete', url, **kwargs) diff --git a/venv/lib/python3.7/site-packages/requests/auth.py b/venv/lib/python3.7/site-packages/requests/auth.py new file mode 100644 index 0000000..bdde51c --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/auth.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- + +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import os +import re +import time +import hashlib +import threading +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 .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(password), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode('latin1') + + if isinstance(password, str): + password = password.encode('latin1') + + authstr = 'Basic ' + to_native_string( + b64encode(b':'.join((username, password))).strip() + ) + + return authstr + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + 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) + return r + + +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) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, 'init'): + self._thread_local.init = True + self._thread_local.last_nonce = '' + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :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') + hash_utf8 = None + + if algorithm is None: + _algorithm = 'MD5' + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': + def md5_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + hash_utf8 = md5_utf8 + elif _algorithm == 'SHA': + def sha_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + hash_utf8 = sha_utf8 + elif _algorithm == 'SHA-256': + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha256(x).hexdigest() + hash_utf8 = sha256_utf8 + elif _algorithm == 'SHA-512': + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha512(x).hexdigest() + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: 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 + + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (method, path) + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + 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') + s += os.urandom(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) + if _algorithm == 'MD5-SESS': + HA1 = hash_utf8('%s:%s:%s' % (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, noncebit) + else: + # XXX handle auth-int. + return None + + 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) + if opaque: + base += ', opaque="%s"' % opaque + if algorithm: + base += ', algorithm="%s"' % algorithm + if entdig: + base += ', digest="%s"' % entdig + if qop: + base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) + + return 'Digest %s' % (base) + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/requests/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # 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', '') + + 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)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + 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) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + 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) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # 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) + 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) + ]) + + def __ne__(self, other): + return not self == other diff --git a/venv/lib/python3.7/site-packages/requests/certs.py b/venv/lib/python3.7/site-packages/requests/certs.py new file mode 100644 index 0000000..d1a378d --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +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 certifi import where + +if __name__ == '__main__': + print(where()) diff --git a/venv/lib/python3.7/site-packages/requests/compat.py b/venv/lib/python3.7/site-packages/requests/compat.py new file mode 100644 index 0000000..c44b35e --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/compat.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module handles import compatibility issues between Python 2 and +Python 3. +""" + +import chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +try: + import simplejson as json +except ImportError: + import json + +# --------- +# 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 + 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) + +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 + from collections import OrderedDict + from collections.abc import Callable, Mapping, MutableMapping + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) + integer_types = (int,) diff --git a/venv/lib/python3.7/site-packages/requests/cookies.py b/venv/lib/python3.7/site-packages/requests/cookies.py new file mode 100644 index 0000000..56fccd9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/cookies.py @@ -0,0 +1,549 @@ +# -*- coding: utf-8 -*- + +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import copy +import time +import calendar + +from ._internal_utils import to_native_string +from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest(object): + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + 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'): + 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') + 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 + ]) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + 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()") + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse(object): + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, '_original_response') and + response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get('Cookie') + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # 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')) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + 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) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + 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) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super(RequestsCookieJar, self).update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + 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: + return toReturn + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + 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__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + 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, + } + + badargs = set(kwargs) - set(result) + if badargs: + err = 'create_cookie() got unexpected keyword arguments: %s' + raise TypeError(err % 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']) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel['max-age']: + try: + 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) + ) + return create_cookie( + comment=morsel['comment'], + comment_url=bool(morsel['comment']), + discard=False, + domain=morsel['domain'], + expires=expires, + name=morsel.key, + path=morsel['path'], + port=None, + rest={'HttpOnly': morsel['httponly']}, + rfc2109=False, + secure=bool(morsel['secure']), + value=morsel.value, + version=morsel['version'] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError('You can only merge into CookieJar') + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict( + cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/venv/lib/python3.7/site-packages/requests/exceptions.py b/venv/lib/python3.7/site-packages/requests/exceptions.py new file mode 100644 index 0000000..a80cad8 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/exceptions.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from urllib3.exceptions import HTTPError as BaseHTTPError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + 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 = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL schema (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """See defaults.py for valid schemas.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body""" + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + pass + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + pass + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" + pass diff --git a/venv/lib/python3.7/site-packages/requests/help.py b/venv/lib/python3.7/site-packages/requests/help.py new file mode 100644 index 0000000..e53d35e --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/help.py @@ -0,0 +1,119 @@ +"""Module containing bug report helper(s).""" +from __future__ import print_function + +import json +import platform +import sys +import ssl + +import idna +import urllib3 +import chardet + +from . import __version__ as requests_version + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import OpenSSL + import cryptography + + +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'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + 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': + implementation_version = platform.python_version() # Complete Guess + elif implementation == 'IronPython': + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = 'Unknown' + + return {'name': implementation, 'version': implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + 'system': platform.system(), + 'release': platform.release(), + } + except IOError: + platform_info = { + 'system': 'Unknown', + 'release': 'Unknown', + } + + implementation_info = _implementation() + urllib3_info = {'version': urllib3.__version__} + chardet_info = {'version': chardet.__version__} + + pyopenssl_info = { + 'version': None, + 'openssl_version': '', + } + if OpenSSL: + pyopenssl_info = { + 'version': OpenSSL.__version__, + 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, + } + cryptography_info = { + 'version': getattr(cryptography, '__version__', ''), + } + idna_info = { + 'version': getattr(idna, '__version__', ''), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = { + 'version': '%x' % system_ssl 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, + 'pyOpenSSL': pyopenssl_info, + 'urllib3': urllib3_info, + 'chardet': chardet_info, + 'cryptography': cryptography_info, + 'idna': idna_info, + 'requests': { + 'version': requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.7/site-packages/requests/hooks.py b/venv/lib/python3.7/site-packages/requests/hooks.py new file mode 100644 index 0000000..7a51f21 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/hooks.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ['response'] + + +def default_hooks(): + return {event: [] for event in HOOKS} + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, '__call__'): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/venv/lib/python3.7/site-packages/requests/models.py b/venv/lib/python3.7/site-packages/requests/models.py new file mode 100644 index 0000000..62dcd0b --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/models.py @@ -0,0 +1,953 @@ +# -*- coding: utf-8 -*- + +""" +requests.models +~~~~~~~~~~~~~~~ + +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/requests/requests/issues/3578. +import encodings.idna + +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url +from 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) +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 .compat import ( + Callable, Mapping, + cookielib, urlunparse, urlsplit, urlencode, str, bytes, + is_py2, chardet, builtin_str, basestring) +from .compat import json as complexjson +from .status_codes import codes + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin(object): + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = '/' + + url.append(path) + + query = p.query + if query: + url.append('?') + url.append(query) + + return ''.join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in to_key_val_list(data): + 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)) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if (not files): + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, '__iter__'): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + 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)) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, 'read'): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin(object): + 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)) + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, '__iter__'): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request <Request>` object. + + Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + <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): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return '<Request [%s]>' % (self.method) + + def prepare(self): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, + containing the exact bytes that will be sent to the server. + + Generated from either a :class:`Request <Request>` object or manually. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + <PreparedRequest [GET]> + + >>> s = requests.Session() + >>> s.send(r) + <Response [200]> + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: 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): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return '<PreparedRequest [%s]>' % (self.method) + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + import idna + + try: + host = idna.encode(host, uts46=True).decode('utf-8') + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/requests/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode('utf8') + else: + url = unicode(url) if is_py2 else str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # 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'): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + 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) + + if not host: + raise InvalidURL("Invalid URL %r: No host supplied" % url) + + # 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 + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + 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.') + + # Carefully reconstruct the network location + netloc = auth or '' + if netloc: + netloc += '@' + netloc += host + if port: + netloc += ':' + str(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') + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = '%s&%s' % (query, enc_params) + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + 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' + body = complexjson.dumps(json) + if not isinstance(body, bytes): + body = body.encode('utf-8') + + is_stream = all([ + hasattr(data, '__iter__'), + not isinstance(data, (basestring, list, tuple, Mapping)) + ]) + + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + if is_stream: + body = data + + 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): + # 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.') + + if length: + self.headers['Content-Length'] = builtin_str(length) + else: + self.headers['Transfer-Encoding'] = 'chunked' + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + 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 + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + 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: + # 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' + + def prepare_auth(self, auth, url=''): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response(object): + """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' + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + # This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response <Response>` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest <PreparedRequest>` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) + + def __repr__(self): + return '<Response [%s]>' % (self.status_code) + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """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) + + @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)) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the chardet library.""" + 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 + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, 'stream'): + try: + for chunk in self.raw.stream(chunk_size, decode_content=True): + yield chunk + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + 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)) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + 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. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + for line in lines: + yield line + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_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_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return str('') + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + 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. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors='replace') + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises ValueError: If the response body does not contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using chardet to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + 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) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get('link') + + # l = MultiDict() + l = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get('rel') or link.get('url') + l[key] = link + + return l + + def raise_for_status(self): + """Raises stored :class:`HTTPError`, if one occurred.""" + + 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') + except UnicodeDecodeError: + 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) + + elif 500 <= self.status_code < 600: + http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, 'release_conn', None) + if release_conn is not None: + release_conn() diff --git a/venv/lib/python3.7/site-packages/requests/packages.py b/venv/lib/python3.7/site-packages/requests/packages.py new file mode 100644 index 0000000..7232fe0 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/packages.py @@ -0,0 +1,14 @@ +import sys + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna', 'chardet'): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(package + '.'): + sys.modules['requests.packages.' + mod] = sys.modules[mod] + +# Kinda cool, though, right? diff --git a/venv/lib/python3.7/site-packages/requests/sessions.py b/venv/lib/python3.7/site-packages/requests/sessions.py new file mode 100644 index 0000000..d73d700 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/sessions.py @@ -0,0 +1,770 @@ +# -*- coding: utf-8 -*- + +""" +requests.session +~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from datetime import timedelta + +from .auth import _basic_auth_str +from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping +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 +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 +) + +from .status_codes import codes + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI + +# 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 +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and + isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get('response') == []: + return request_hooks + + 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): + + 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 + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + 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') + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # 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)): + 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): + 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): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith('//'): + parsed_rurl = urlparse(resp.url) + url = '%s:%s' % (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: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/requests/requests/issues/1084 + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): + # https://github.com/requests/requests/issues/3490 + 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 + try: + del headers['Cookie'] + except KeyError: + pass + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # 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) + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.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'] + + # .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) + + return + + 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 + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :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') + + 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'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + 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' + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + 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' + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + <Response [200]> + + Or as a context manager:: + + >>> with requests.Session() as s: + >>> s.get('https://httpbin.org/get') + <Response [200]> + """ + + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'max_redirects', + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request <Request>` sent from this + #: :class:`Session <Session>`. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request <Request>`. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request <Request>`. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request <Request>`. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', HTTPAdapter()) + self.mount('http://', HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request <Request>` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + 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, + hooks=merge_hooks(request.hooks, self.hooks), + ) + 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): + """Constructs a :class:`Request <Request>`, prepares it and sends it. + Returns :class:`Response <Response>` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + 'timeout': timeout, + 'allow_redirects': allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + 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. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PUT', url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PATCH', url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('DELETE', url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # 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.proxies) + + # 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.') + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + + # Resolve redirects if allowed. + history = [resp for resp in gen] if allow_redirects else [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # 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)) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # 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 + 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. + if verify is True or verify is None: + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema("No connection adapters were found for '%s'" % url) + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/venv/lib/python3.7/site-packages/requests/status_codes.py b/venv/lib/python3.7/site-packages/requests/status_codes.py new file mode 100644 index 0000000..813e8c4 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/status_codes.py @@ -0,0 +1,120 @@ +# -*- 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 +items. + +>>> requests.codes['temporary_redirect'] +307 +>>> requests.codes.teapot +418 +>>> requests.codes['\o/'] +200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +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',), + + # 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 + + # 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',), + + # 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'), +} + +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(('\\', '/')): + setattr(codes, title.upper(), code) + + def doc(code): + names = ', '.join('``%s``' % 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) + +_init() diff --git a/venv/lib/python3.7/site-packages/requests/structures.py b/venv/lib/python3.7/site-packages/requests/structures.py new file mode 100644 index 0000000..da930e2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/structures.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from .compat import OrderedDict, Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super(LookupDict, self).__init__() + + def __repr__(self): + return '<lookup \'%s\'>' % (self.name) + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/venv/lib/python3.7/site-packages/requests/utils.py b/venv/lib/python3.7/site-packages/requests/utils.py new file mode 100644 index 0000000..8170a8d --- /dev/null +++ b/venv/lib/python3.7/site-packages/requests/utils.py @@ -0,0 +1,977 @@ +# -*- coding: utf-8 -*- + +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile + +from .__version__ import __version__ +from . import certs +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import to_native_string +from .compat import parse_http_list as _parse_list_header +from .compat import ( + quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, + proxy_bypass, urlunparse, basestring, integer_types, is_py3, + proxy_bypass_environment, getproxies_environment, Mapping) +from .cookies import cookiejar_from_dict +from .structures import CaseInsensitiveDict +from .exceptions import ( + InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) + +NETRC_FILES = ('.netrc', '_netrc') + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {'http': 80, 'https': 443} + + +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 + except ImportError: + return False + + try: + 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]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, + 'ProxyOverride')[0] + except OSError: + return False + if not proxyEnable or not proxyOverride: + return False + + # 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(';') + # now check if we match one of the registry values. + for test in proxyOverride: + 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 + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, '__len__'): + total_length = len(o) + + elif hasattr(o, 'len'): + total_length = o.len + + elif hasattr(o, 'fileno'): + try: + fileno = o.fileno() + except io.UnsupportedOperation: + 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 hasattr(o, 'tell'): + try: + current_position = o.tell() + except (OSError, IOError): + # 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 + # let requests chunk it instead. + 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 + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except (OSError, IOError): + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + try: + from netrc import netrc, NetrcParseError + + netrc_path = None + + for f in NETRC_FILES: + try: + loc = os.path.expanduser('~/{}'.format(f)) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/requests/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b':' + if isinstance(url, str): + 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) + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, IOError): + # 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: + raise + + # AppEngine hackiness. + except (ImportError, AttributeError): + pass + + +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] != '>'): + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + 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 zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return 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('/')) + if not os.path.exists(extracted_path): + extracted_path = zip_file.extract(member, path=tmp) + + return extracted_path + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples. + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if '=' not in item: + result[item] = None + continue + name, value = item.split('=', 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # 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('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :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) + + 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)) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(';') + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + 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) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + 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 'text' in content_type: + return 'ISO-8859-1' + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes a iterator.""" + + if r.encoding is None: + for item in iterator: + yield item + return + + 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) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos:pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :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) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors='replace') + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + 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) + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = '%' + parts[i] + else: + parts[i] = '%' + parts[i] + return ''.join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :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 + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xffffffff ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack('>I', bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except socket.error: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count('/') == 1: + try: + mask = int(string_network.split('/')[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split('/')[0]) + except socket.error: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # 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()) + + # 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') + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + 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 + ) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += ':{}'.format(parsed.port) + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + 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) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get('all')) + + proxy_keys = [ + urlparts.scheme + '://' + urlparts.hostname, + urlparts.scheme, + 'all://' + urlparts.hostname, + 'all', + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return '%s/%s' % (name, __version__) + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict({ + 'User-Agent': default_user_agent(), + 'Accept-Encoding': ', '.join(('gzip', 'deflate')), + 'Accept': '*/*', + 'Connection': 'keep-alive', + }) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = ' \'"' + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(', *<', value): + try: + url, params = val.split(';', 1) + except ValueError: + url, params = val, '' + + link = {'url': url.strip('<> \'"')} + + for param in params.split(';'): + try: + key, value = param.split('=') + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # 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 + if sample[:3] == codecs.BOM_UTF8: + 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 + nullcount = sample.count(_null) + if nullcount == 0: + return 'utf-8' + if nullcount == 2: + 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' + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return 'utf-32-be' + if sample[1:] == _null3: + return 'utf-32-le' + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :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. + if not netloc: + netloc, path = path, netloc + + return urlunparse((scheme, netloc, path, params, query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + 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. + + :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))) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + 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): + try: + body_seek(prepared_request._body_position) + except (IOError, 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/venv/lib/python3.7/site-packages/setuptools-40.8.0-py3.7.egg b/venv/lib/python3.7/site-packages/setuptools-40.8.0-py3.7.egg new file mode 100644 index 0000000000000000000000000000000000000000..a3a7473b757ec3ba5a7b130011ff1e35c3f3738d GIT binary patch literal 571891 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𝔰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%0AG%7>LGAv`MYY6dsnr?ceR@+f%@+@T5v?a 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%0DUrlBtk5GAOV(MZGQ>>wI5qz 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%0AbBVWk;%%^aTOi(S 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�|&dmFJ$ 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%0ArzIR;&LesFz1~|9LH>l* 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�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%0AG%7>LGAv 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.7/site-packages/setuptools.pth b/venv/lib/python3.7/site-packages/setuptools.pth new file mode 100644 index 0000000..ca49991 --- /dev/null +++ b/venv/lib/python3.7/site-packages/setuptools.pth @@ -0,0 +1 @@ +./setuptools-40.8.0-py3.7.egg diff --git a/venv/lib/python3.7/site-packages/tornado/__init__.py b/venv/lib/python3.7/site-packages/tornado/__init__.py new file mode 100644 index 0000000..a1094a6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/__init__.py @@ -0,0 +1,26 @@ +# +# Copyright 2009 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. + +"""The Tornado web server and tools.""" + +# version is a human-readable version number. + +# version_info is a four-tuple for programmatic comparison. The first +# three numbers are the components of the version number. The fourth +# is zero for an official release, positive for a development branch, +# or negative for a release candidate or beta (after the base version +# number has been incremented) +version = "6.0.3" +version_info = (6, 0, 3, 0) diff --git a/venv/lib/python3.7/site-packages/tornado/_locale_data.py b/venv/lib/python3.7/site-packages/tornado/_locale_data.py new file mode 100644 index 0000000..91416d9 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/_locale_data.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# +# 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. + +"""Data used by the tornado.locale module.""" + +LOCALE_NAMES = { + "af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"}, + "am_ET": {"name_en": u"Amharic", "name": u"አማርኛ"}, + "ar_AR": {"name_en": u"Arabic", "name": u"العربية"}, + "bg_BG": {"name_en": u"Bulgarian", "name": u"Български"}, + "bn_IN": {"name_en": u"Bengali", "name": u"বাংলা"}, + "bs_BA": {"name_en": u"Bosnian", "name": u"Bosanski"}, + "ca_ES": {"name_en": u"Catalan", "name": u"Català"}, + "cs_CZ": {"name_en": u"Czech", "name": u"Čeština"}, + "cy_GB": {"name_en": u"Welsh", "name": u"Cymraeg"}, + "da_DK": {"name_en": u"Danish", "name": u"Dansk"}, + "de_DE": {"name_en": u"German", "name": u"Deutsch"}, + "el_GR": {"name_en": u"Greek", "name": u"Ελληνικά"}, + "en_GB": {"name_en": u"English (UK)", "name": u"English (UK)"}, + "en_US": {"name_en": u"English (US)", "name": u"English (US)"}, + "es_ES": {"name_en": u"Spanish (Spain)", "name": u"Español (España)"}, + "es_LA": {"name_en": u"Spanish", "name": u"Español"}, + "et_EE": {"name_en": u"Estonian", "name": u"Eesti"}, + "eu_ES": {"name_en": u"Basque", "name": u"Euskara"}, + "fa_IR": {"name_en": u"Persian", "name": u"فارسی"}, + "fi_FI": {"name_en": u"Finnish", "name": u"Suomi"}, + "fr_CA": {"name_en": u"French (Canada)", "name": u"Français (Canada)"}, + "fr_FR": {"name_en": u"French", "name": u"Français"}, + "ga_IE": {"name_en": u"Irish", "name": u"Gaeilge"}, + "gl_ES": {"name_en": u"Galician", "name": u"Galego"}, + "he_IL": {"name_en": u"Hebrew", "name": u"עברית"}, + "hi_IN": {"name_en": u"Hindi", "name": u"हिन्दी"}, + "hr_HR": {"name_en": u"Croatian", "name": u"Hrvatski"}, + "hu_HU": {"name_en": u"Hungarian", "name": u"Magyar"}, + "id_ID": {"name_en": u"Indonesian", "name": u"Bahasa Indonesia"}, + "is_IS": {"name_en": u"Icelandic", "name": u"Íslenska"}, + "it_IT": {"name_en": u"Italian", "name": u"Italiano"}, + "ja_JP": {"name_en": u"Japanese", "name": u"日本語"}, + "ko_KR": {"name_en": u"Korean", "name": u"한국어"}, + "lt_LT": {"name_en": u"Lithuanian", "name": u"Lietuvių"}, + "lv_LV": {"name_en": u"Latvian", "name": u"Latviešu"}, + "mk_MK": {"name_en": u"Macedonian", "name": u"Македонски"}, + "ml_IN": {"name_en": u"Malayalam", "name": u"മലയാളം"}, + "ms_MY": {"name_en": u"Malay", "name": u"Bahasa Melayu"}, + "nb_NO": {"name_en": u"Norwegian (bokmal)", "name": u"Norsk (bokmål)"}, + "nl_NL": {"name_en": u"Dutch", "name": u"Nederlands"}, + "nn_NO": {"name_en": u"Norwegian (nynorsk)", "name": u"Norsk (nynorsk)"}, + "pa_IN": {"name_en": u"Punjabi", "name": u"ਪੰਜਾਬੀ"}, + "pl_PL": {"name_en": u"Polish", "name": u"Polski"}, + "pt_BR": {"name_en": u"Portuguese (Brazil)", "name": u"Português (Brasil)"}, + "pt_PT": {"name_en": u"Portuguese (Portugal)", "name": u"Português (Portugal)"}, + "ro_RO": {"name_en": u"Romanian", "name": u"Română"}, + "ru_RU": {"name_en": u"Russian", "name": u"Русский"}, + "sk_SK": {"name_en": u"Slovak", "name": u"Slovenčina"}, + "sl_SI": {"name_en": u"Slovenian", "name": u"Slovenščina"}, + "sq_AL": {"name_en": u"Albanian", "name": u"Shqip"}, + "sr_RS": {"name_en": u"Serbian", "name": u"Српски"}, + "sv_SE": {"name_en": u"Swedish", "name": u"Svenska"}, + "sw_KE": {"name_en": u"Swahili", "name": u"Kiswahili"}, + "ta_IN": {"name_en": u"Tamil", "name": u"தமிழ்"}, + "te_IN": {"name_en": u"Telugu", "name": u"తెలుగు"}, + "th_TH": {"name_en": u"Thai", "name": u"ภาษาไทย"}, + "tl_PH": {"name_en": u"Filipino", "name": u"Filipino"}, + "tr_TR": {"name_en": u"Turkish", "name": u"Türkçe"}, + "uk_UA": {"name_en": u"Ukraini ", "name": u"Українська"}, + "vi_VN": {"name_en": u"Vietnamese", "name": u"Tiếng Việt"}, + "zh_CN": {"name_en": u"Chinese (Simplified)", "name": u"中文(简体)"}, + "zh_TW": {"name_en": u"Chinese (Traditional)", "name": u"中文(繁體)"}, +} diff --git a/venv/lib/python3.7/site-packages/tornado/auth.py b/venv/lib/python3.7/site-packages/tornado/auth.py new file mode 100644 index 0000000..db6d290 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/auth.py @@ -0,0 +1,1182 @@ +# +# Copyright 2009 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. + +"""This module contains implementations of various third-party +authentication schemes. + +All the classes in this file are class mixins designed to be used with +the `tornado.web.RequestHandler` class. They are used in two ways: + +* On a login handler, use methods such as ``authenticate_redirect()``, + ``authorize_redirect()``, and ``get_authenticated_user()`` to + establish the user's identity and store authentication tokens to your + database and/or cookies. +* In non-login handlers, use methods such as ``facebook_request()`` + or ``twitter_request()`` to use the authentication tokens to make + requests to the respective services. + +They all take slightly different arguments due to the fact all these +services implement authentication and authorization slightly differently. +See the individual service classes below for complete documentation. + +Example usage for Google OAuth: + +.. testcode:: + + class GoogleOAuth2LoginHandler(tornado.web.RequestHandler, + tornado.auth.GoogleOAuth2Mixin): + async def get(self): + if self.get_argument('code', False): + user = await self.get_authenticated_user( + redirect_uri='http://your.site.com/auth/google', + code=self.get_argument('code')) + # Save the user with e.g. set_secure_cookie + else: + await self.authorize_redirect( + redirect_uri='http://your.site.com/auth/google', + client_id=self.settings['google_oauth']['key'], + scope=['profile', 'email'], + response_type='code', + extra_params={'approval_prompt': 'auto'}) + +.. testoutput:: + :hide: + +""" + +import base64 +import binascii +import hashlib +import hmac +import time +import urllib.parse +import uuid + +from tornado import httpclient +from tornado import escape +from tornado.httputil import url_concat +from tornado.util import unicode_type +from tornado.web import RequestHandler + +from typing import List, Any, Dict, cast, Iterable, Union, Optional + + +class AuthError(Exception): + pass + + +class OpenIdMixin(object): + """Abstract implementation of OpenID and Attribute Exchange. + + Class attributes: + + * ``_OPENID_ENDPOINT``: the identity provider's URI. + """ + + def authenticate_redirect( + self, + callback_uri: str = None, + ax_attrs: List[str] = ["name", "email", "language", "username"], + ) -> None: + """Redirects to the authentication URL for this service. + + After authentication, the service will redirect back to the given + callback URI with additional parameters including ``openid.mode``. + + We request the given attributes for the authenticated user by + default (name, email, language, and username). If you don't need + all those attributes for your app, you can request fewer with + the ax_attrs keyword argument. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed and this method no + longer returns an awaitable object. It is now an ordinary + synchronous function. + """ + handler = cast(RequestHandler, self) + callback_uri = callback_uri or handler.request.uri + assert callback_uri is not None + args = self._openid_args(callback_uri, ax_attrs=ax_attrs) + endpoint = self._OPENID_ENDPOINT # type: ignore + handler.redirect(endpoint + "?" + urllib.parse.urlencode(args)) + + async def get_authenticated_user( + self, http_client: httpclient.AsyncHTTPClient = None + ) -> Dict[str, Any]: + """Fetches the authenticated user data upon redirect. + + This method should be called by the handler that receives the + redirect from the `authenticate_redirect()` method (which is + often the same as the one that calls it; in that case you would + call `get_authenticated_user` if the ``openid.mode`` parameter + is present and `authenticate_redirect` if it is not). + + The result of this method will generally be used to set a cookie. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + awaitable object instead. + """ + handler = cast(RequestHandler, self) + # Verify the OpenID response via direct request to the OP + args = dict( + (k, v[-1]) for k, v in handler.request.arguments.items() + ) # type: Dict[str, Union[str, bytes]] + args["openid.mode"] = u"check_authentication" + url = self._OPENID_ENDPOINT # type: ignore + if http_client is None: + http_client = self.get_auth_http_client() + resp = await http_client.fetch( + url, method="POST", body=urllib.parse.urlencode(args) + ) + return self._on_authentication_verified(resp) + + def _openid_args( + self, callback_uri: str, ax_attrs: Iterable[str] = [], oauth_scope: str = None + ) -> Dict[str, str]: + handler = cast(RequestHandler, self) + url = urllib.parse.urljoin(handler.request.full_url(), callback_uri) + args = { + "openid.ns": "http://specs.openid.net/auth/2.0", + "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select", + "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select", + "openid.return_to": url, + "openid.realm": urllib.parse.urljoin(url, "/"), + "openid.mode": "checkid_setup", + } + if ax_attrs: + args.update( + { + "openid.ns.ax": "http://openid.net/srv/ax/1.0", + "openid.ax.mode": "fetch_request", + } + ) + ax_attrs = set(ax_attrs) + required = [] # type: List[str] + if "name" in ax_attrs: + ax_attrs -= set(["name", "firstname", "fullname", "lastname"]) + required += ["firstname", "fullname", "lastname"] + args.update( + { + "openid.ax.type.firstname": "http://axschema.org/namePerson/first", + "openid.ax.type.fullname": "http://axschema.org/namePerson", + "openid.ax.type.lastname": "http://axschema.org/namePerson/last", + } + ) + known_attrs = { + "email": "http://axschema.org/contact/email", + "language": "http://axschema.org/pref/language", + "username": "http://axschema.org/namePerson/friendly", + } + for name in ax_attrs: + args["openid.ax.type." + name] = known_attrs[name] + required.append(name) + args["openid.ax.required"] = ",".join(required) + if oauth_scope: + args.update( + { + "openid.ns.oauth": "http://specs.openid.net/extensions/oauth/1.0", + "openid.oauth.consumer": handler.request.host.split(":")[0], + "openid.oauth.scope": oauth_scope, + } + ) + return args + + def _on_authentication_verified( + self, response: httpclient.HTTPResponse + ) -> Dict[str, Any]: + handler = cast(RequestHandler, self) + if b"is_valid:true" not in response.body: + raise AuthError("Invalid OpenID response: %s" % response.body) + + # Make sure we got back at least an email from attribute exchange + ax_ns = None + for key in handler.request.arguments: + if ( + key.startswith("openid.ns.") + and handler.get_argument(key) == u"http://openid.net/srv/ax/1.0" + ): + ax_ns = key[10:] + break + + def get_ax_arg(uri: str) -> str: + if not ax_ns: + return u"" + prefix = "openid." + ax_ns + ".type." + ax_name = None + for name in handler.request.arguments.keys(): + if handler.get_argument(name) == uri and name.startswith(prefix): + part = name[len(prefix) :] + ax_name = "openid." + ax_ns + ".value." + part + break + if not ax_name: + return u"" + return handler.get_argument(ax_name, u"") + + email = get_ax_arg("http://axschema.org/contact/email") + name = get_ax_arg("http://axschema.org/namePerson") + first_name = get_ax_arg("http://axschema.org/namePerson/first") + last_name = get_ax_arg("http://axschema.org/namePerson/last") + username = get_ax_arg("http://axschema.org/namePerson/friendly") + locale = get_ax_arg("http://axschema.org/pref/language").lower() + user = dict() + name_parts = [] + if first_name: + user["first_name"] = first_name + name_parts.append(first_name) + if last_name: + user["last_name"] = last_name + name_parts.append(last_name) + if name: + user["name"] = name + elif name_parts: + user["name"] = u" ".join(name_parts) + elif email: + user["name"] = email.split("@")[0] + if email: + user["email"] = email + if locale: + user["locale"] = locale + if username: + user["username"] = username + claimed_id = handler.get_argument("openid.claimed_id", None) + if claimed_id: + user["claimed_id"] = claimed_id + return user + + def get_auth_http_client(self) -> httpclient.AsyncHTTPClient: + """Returns the `.AsyncHTTPClient` instance to be used for auth requests. + + May be overridden by subclasses to use an HTTP client other than + the default. + """ + return httpclient.AsyncHTTPClient() + + +class OAuthMixin(object): + """Abstract implementation of OAuth 1.0 and 1.0a. + + See `TwitterMixin` below for an example implementation. + + Class attributes: + + * ``_OAUTH_AUTHORIZE_URL``: The service's OAuth authorization url. + * ``_OAUTH_ACCESS_TOKEN_URL``: The service's OAuth access token url. + * ``_OAUTH_VERSION``: May be either "1.0" or "1.0a". + * ``_OAUTH_NO_CALLBACKS``: Set this to True if the service requires + advance registration of callbacks. + + Subclasses must also override the `_oauth_get_user_future` and + `_oauth_consumer_token` methods. + """ + + async def authorize_redirect( + self, + callback_uri: str = None, + extra_params: Dict[str, Any] = None, + http_client: httpclient.AsyncHTTPClient = None, + ) -> None: + """Redirects the user to obtain OAuth authorization for this service. + + The ``callback_uri`` may be omitted if you have previously + registered a callback URI with the third-party service. For + some services, you must use a previously-registered callback + URI and cannot specify a callback via this method. + + This method sets a cookie called ``_oauth_request_token`` which is + subsequently used (and cleared) in `get_authenticated_user` for + security purposes. + + This method is asynchronous and must be called with ``await`` + or ``yield`` (This is different from other ``auth*_redirect`` + methods defined in this module). It calls + `.RequestHandler.finish` for you so you should not write any + other response after it returns. + + .. versionchanged:: 3.1 + Now returns a `.Future` and takes an optional callback, for + compatibility with `.gen.coroutine`. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + awaitable object instead. + + """ + if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False): + raise Exception("This service does not support oauth_callback") + if http_client is None: + http_client = self.get_auth_http_client() + assert http_client is not None + if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": + response = await http_client.fetch( + self._oauth_request_token_url( + callback_uri=callback_uri, extra_params=extra_params + ) + ) + else: + response = await http_client.fetch(self._oauth_request_token_url()) + url = self._OAUTH_AUTHORIZE_URL # type: ignore + self._on_request_token(url, callback_uri, response) + + async def get_authenticated_user( + self, http_client: httpclient.AsyncHTTPClient = None + ) -> Dict[str, Any]: + """Gets the OAuth authorized user and access token. + + This method should be called from the handler for your + OAuth callback URL to complete the registration process. We run the + callback with the authenticated user dictionary. This dictionary + will contain an ``access_key`` which can be used to make authorized + requests to this service on behalf of the user. The dictionary will + also contain other fields such as ``name``, depending on the service + used. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + awaitable object instead. + """ + handler = cast(RequestHandler, self) + request_key = escape.utf8(handler.get_argument("oauth_token")) + oauth_verifier = handler.get_argument("oauth_verifier", None) + request_cookie = handler.get_cookie("_oauth_request_token") + if not request_cookie: + raise AuthError("Missing OAuth request token cookie") + handler.clear_cookie("_oauth_request_token") + cookie_key, cookie_secret = [ + base64.b64decode(escape.utf8(i)) for i in request_cookie.split("|") + ] + if cookie_key != request_key: + raise AuthError("Request token does not match cookie") + token = dict( + key=cookie_key, secret=cookie_secret + ) # type: Dict[str, Union[str, bytes]] + if oauth_verifier: + token["verifier"] = oauth_verifier + if http_client is None: + http_client = self.get_auth_http_client() + assert http_client is not None + response = await http_client.fetch(self._oauth_access_token_url(token)) + access_token = _oauth_parse_response(response.body) + user = await self._oauth_get_user_future(access_token) + if not user: + raise AuthError("Error getting user") + user["access_token"] = access_token + return user + + def _oauth_request_token_url( + self, callback_uri: str = None, extra_params: Dict[str, Any] = None + ) -> str: + handler = cast(RequestHandler, self) + consumer_token = self._oauth_consumer_token() + url = self._OAUTH_REQUEST_TOKEN_URL # type: ignore + args = dict( + oauth_consumer_key=escape.to_basestring(consumer_token["key"]), + oauth_signature_method="HMAC-SHA1", + oauth_timestamp=str(int(time.time())), + oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)), + oauth_version="1.0", + ) + if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": + if callback_uri == "oob": + args["oauth_callback"] = "oob" + elif callback_uri: + args["oauth_callback"] = urllib.parse.urljoin( + handler.request.full_url(), callback_uri + ) + if extra_params: + args.update(extra_params) + signature = _oauth10a_signature(consumer_token, "GET", url, args) + else: + signature = _oauth_signature(consumer_token, "GET", url, args) + + args["oauth_signature"] = signature + return url + "?" + urllib.parse.urlencode(args) + + def _on_request_token( + self, + authorize_url: str, + callback_uri: Optional[str], + response: httpclient.HTTPResponse, + ) -> None: + handler = cast(RequestHandler, self) + request_token = _oauth_parse_response(response.body) + data = ( + base64.b64encode(escape.utf8(request_token["key"])) + + b"|" + + base64.b64encode(escape.utf8(request_token["secret"])) + ) + handler.set_cookie("_oauth_request_token", data) + args = dict(oauth_token=request_token["key"]) + if callback_uri == "oob": + handler.finish(authorize_url + "?" + urllib.parse.urlencode(args)) + return + elif callback_uri: + args["oauth_callback"] = urllib.parse.urljoin( + handler.request.full_url(), callback_uri + ) + handler.redirect(authorize_url + "?" + urllib.parse.urlencode(args)) + + def _oauth_access_token_url(self, request_token: Dict[str, Any]) -> str: + consumer_token = self._oauth_consumer_token() + url = self._OAUTH_ACCESS_TOKEN_URL # type: ignore + args = dict( + oauth_consumer_key=escape.to_basestring(consumer_token["key"]), + oauth_token=escape.to_basestring(request_token["key"]), + oauth_signature_method="HMAC-SHA1", + oauth_timestamp=str(int(time.time())), + oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)), + oauth_version="1.0", + ) + if "verifier" in request_token: + args["oauth_verifier"] = request_token["verifier"] + + if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": + signature = _oauth10a_signature( + consumer_token, "GET", url, args, request_token + ) + else: + signature = _oauth_signature( + consumer_token, "GET", url, args, request_token + ) + + args["oauth_signature"] = signature + return url + "?" + urllib.parse.urlencode(args) + + def _oauth_consumer_token(self) -> Dict[str, Any]: + """Subclasses must override this to return their OAuth consumer keys. + + The return value should be a `dict` with keys ``key`` and ``secret``. + """ + raise NotImplementedError() + + async def _oauth_get_user_future( + self, access_token: Dict[str, Any] + ) -> Dict[str, Any]: + """Subclasses must override this to get basic information about the + user. + + Should be a coroutine whose result is a dictionary + containing information about the user, which may have been + retrieved by using ``access_token`` to make a request to the + service. + + The access token will be added to the returned dictionary to make + the result of `get_authenticated_user`. + + .. versionchanged:: 5.1 + + Subclasses may also define this method with ``async def``. + + .. versionchanged:: 6.0 + + A synchronous fallback to ``_oauth_get_user`` was removed. + """ + raise NotImplementedError() + + def _oauth_request_parameters( + self, + url: str, + access_token: Dict[str, Any], + parameters: Dict[str, Any] = {}, + method: str = "GET", + ) -> Dict[str, Any]: + """Returns the OAuth parameters as a dict for the given request. + + parameters should include all POST arguments and query string arguments + that will be sent with the request. + """ + consumer_token = self._oauth_consumer_token() + base_args = dict( + oauth_consumer_key=escape.to_basestring(consumer_token["key"]), + oauth_token=escape.to_basestring(access_token["key"]), + oauth_signature_method="HMAC-SHA1", + oauth_timestamp=str(int(time.time())), + oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)), + oauth_version="1.0", + ) + args = {} + args.update(base_args) + args.update(parameters) + if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": + signature = _oauth10a_signature( + consumer_token, method, url, args, access_token + ) + else: + signature = _oauth_signature( + consumer_token, method, url, args, access_token + ) + base_args["oauth_signature"] = escape.to_basestring(signature) + return base_args + + def get_auth_http_client(self) -> httpclient.AsyncHTTPClient: + """Returns the `.AsyncHTTPClient` instance to be used for auth requests. + + May be overridden by subclasses to use an HTTP client other than + the default. + """ + return httpclient.AsyncHTTPClient() + + +class OAuth2Mixin(object): + """Abstract implementation of OAuth 2.0. + + See `FacebookGraphMixin` or `GoogleOAuth2Mixin` below for example + implementations. + + Class attributes: + + * ``_OAUTH_AUTHORIZE_URL``: The service's authorization url. + * ``_OAUTH_ACCESS_TOKEN_URL``: The service's access token url. + """ + + def authorize_redirect( + self, + redirect_uri: str = None, + client_id: str = None, + client_secret: str = None, + extra_params: Dict[str, Any] = None, + scope: str = None, + response_type: str = "code", + ) -> None: + """Redirects the user to obtain OAuth authorization for this service. + + Some providers require that you register a redirect URL with + your application instead of passing one via this method. You + should call this method to log the user in, and then call + ``get_authenticated_user`` in the handler for your + redirect URL to complete the authorization process. + + .. versionchanged:: 6.0 + + The ``callback`` argument and returned awaitable were removed; + this is now an ordinary synchronous function. + """ + handler = cast(RequestHandler, self) + args = {"response_type": response_type} + if redirect_uri is not None: + args["redirect_uri"] = redirect_uri + if client_id is not None: + args["client_id"] = client_id + if extra_params: + args.update(extra_params) + if scope: + args["scope"] = " ".join(scope) + url = self._OAUTH_AUTHORIZE_URL # type: ignore + handler.redirect(url_concat(url, args)) + + def _oauth_request_token_url( + self, + redirect_uri: str = None, + client_id: str = None, + client_secret: str = None, + code: str = None, + extra_params: Dict[str, Any] = None, + ) -> str: + url = self._OAUTH_ACCESS_TOKEN_URL # type: ignore + args = {} # type: Dict[str, str] + if redirect_uri is not None: + args["redirect_uri"] = redirect_uri + if code is not None: + args["code"] = code + if client_id is not None: + args["client_id"] = client_id + if client_secret is not None: + args["client_secret"] = client_secret + if extra_params: + args.update(extra_params) + return url_concat(url, args) + + async def oauth2_request( + self, + url: str, + access_token: str = None, + post_args: Dict[str, Any] = None, + **args: Any + ) -> Any: + """Fetches the given URL auth an OAuth2 access token. + + If the request is a POST, ``post_args`` should be provided. Query + string arguments should be given as keyword arguments. + + Example usage: + + ..testcode:: + + class MainHandler(tornado.web.RequestHandler, + tornado.auth.FacebookGraphMixin): + @tornado.web.authenticated + async def get(self): + new_entry = await self.oauth2_request( + "https://graph.facebook.com/me/feed", + post_args={"message": "I am posting from my Tornado application!"}, + access_token=self.current_user["access_token"]) + + if not new_entry: + # Call failed; perhaps missing permission? + await self.authorize_redirect() + return + self.finish("Posted a message!") + + .. testoutput:: + :hide: + + .. versionadded:: 4.3 + + .. versionchanged::: 6.0 + + The ``callback`` argument was removed. Use the returned awaitable object instead. + """ + all_args = {} + if access_token: + all_args["access_token"] = access_token + all_args.update(args) + + if all_args: + url += "?" + urllib.parse.urlencode(all_args) + http = self.get_auth_http_client() + if post_args is not None: + response = await http.fetch( + url, method="POST", body=urllib.parse.urlencode(post_args) + ) + else: + response = await http.fetch(url) + return escape.json_decode(response.body) + + def get_auth_http_client(self) -> httpclient.AsyncHTTPClient: + """Returns the `.AsyncHTTPClient` instance to be used for auth requests. + + May be overridden by subclasses to use an HTTP client other than + the default. + + .. versionadded:: 4.3 + """ + return httpclient.AsyncHTTPClient() + + +class TwitterMixin(OAuthMixin): + """Twitter OAuth authentication. + + To authenticate with Twitter, register your application with + Twitter at http://twitter.com/apps. Then copy your Consumer Key + and Consumer Secret to the application + `~tornado.web.Application.settings` ``twitter_consumer_key`` and + ``twitter_consumer_secret``. Use this mixin on the handler for the + URL you registered as your application's callback URL. + + When your application is set up, you can use this mixin like this + to authenticate the user with Twitter and get access to their stream: + + .. testcode:: + + class TwitterLoginHandler(tornado.web.RequestHandler, + tornado.auth.TwitterMixin): + async def get(self): + if self.get_argument("oauth_token", None): + user = await self.get_authenticated_user() + # Save the user using e.g. set_secure_cookie() + else: + await self.authorize_redirect() + + .. testoutput:: + :hide: + + The user object returned by `~OAuthMixin.get_authenticated_user` + includes the attributes ``username``, ``name``, ``access_token``, + and all of the custom Twitter user attributes described at + https://dev.twitter.com/docs/api/1.1/get/users/show + """ + + _OAUTH_REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token" + _OAUTH_ACCESS_TOKEN_URL = "https://api.twitter.com/oauth/access_token" + _OAUTH_AUTHORIZE_URL = "https://api.twitter.com/oauth/authorize" + _OAUTH_AUTHENTICATE_URL = "https://api.twitter.com/oauth/authenticate" + _OAUTH_NO_CALLBACKS = False + _TWITTER_BASE_URL = "https://api.twitter.com/1.1" + + async def authenticate_redirect(self, callback_uri: str = None) -> None: + """Just like `~OAuthMixin.authorize_redirect`, but + auto-redirects if authorized. + + This is generally the right interface to use if you are using + Twitter for single-sign on. + + .. versionchanged:: 3.1 + Now returns a `.Future` and takes an optional callback, for + compatibility with `.gen.coroutine`. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + awaitable object instead. + """ + http = self.get_auth_http_client() + response = await http.fetch( + self._oauth_request_token_url(callback_uri=callback_uri) + ) + self._on_request_token(self._OAUTH_AUTHENTICATE_URL, None, response) + + async def twitter_request( + self, + path: str, + access_token: Dict[str, Any], + post_args: Dict[str, Any] = None, + **args: Any + ) -> Any: + """Fetches the given API path, e.g., ``statuses/user_timeline/btaylor`` + + The path should not include the format or API version number. + (we automatically use JSON format and API version 1). + + If the request is a POST, ``post_args`` should be provided. Query + string arguments should be given as keyword arguments. + + All the Twitter methods are documented at http://dev.twitter.com/ + + Many methods require an OAuth access token which you can + obtain through `~OAuthMixin.authorize_redirect` and + `~OAuthMixin.get_authenticated_user`. The user returned through that + process includes an 'access_token' attribute that can be used + to make authenticated requests via this method. Example + usage: + + .. testcode:: + + class MainHandler(tornado.web.RequestHandler, + tornado.auth.TwitterMixin): + @tornado.web.authenticated + async def get(self): + new_entry = await self.twitter_request( + "/statuses/update", + post_args={"status": "Testing Tornado Web Server"}, + access_token=self.current_user["access_token"]) + if not new_entry: + # Call failed; perhaps missing permission? + yield self.authorize_redirect() + return + self.finish("Posted a message!") + + .. testoutput:: + :hide: + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + awaitable object instead. + """ + if path.startswith("http:") or path.startswith("https:"): + # Raw urls are useful for e.g. search which doesn't follow the + # usual pattern: http://search.twitter.com/search.json + url = path + else: + url = self._TWITTER_BASE_URL + path + ".json" + # Add the OAuth resource request signature if we have credentials + if access_token: + all_args = {} + all_args.update(args) + all_args.update(post_args or {}) + method = "POST" if post_args is not None else "GET" + oauth = self._oauth_request_parameters( + url, access_token, all_args, method=method + ) + args.update(oauth) + if args: + url += "?" + urllib.parse.urlencode(args) + http = self.get_auth_http_client() + if post_args is not None: + response = await http.fetch( + url, method="POST", body=urllib.parse.urlencode(post_args) + ) + else: + response = await http.fetch(url) + return escape.json_decode(response.body) + + def _oauth_consumer_token(self) -> Dict[str, Any]: + handler = cast(RequestHandler, self) + handler.require_setting("twitter_consumer_key", "Twitter OAuth") + handler.require_setting("twitter_consumer_secret", "Twitter OAuth") + return dict( + key=handler.settings["twitter_consumer_key"], + secret=handler.settings["twitter_consumer_secret"], + ) + + async def _oauth_get_user_future( + self, access_token: Dict[str, Any] + ) -> Dict[str, Any]: + user = await self.twitter_request( + "/account/verify_credentials", access_token=access_token + ) + if user: + user["username"] = user["screen_name"] + return user + + +class GoogleOAuth2Mixin(OAuth2Mixin): + """Google authentication using OAuth2. + + In order to use, register your application with Google and copy the + relevant parameters to your application settings. + + * Go to the Google Dev Console at http://console.developers.google.com + * Select a project, or create a new one. + * In the sidebar on the left, select APIs & Auth. + * In the list of APIs, find the Google+ API service and set it to ON. + * In the sidebar on the left, select Credentials. + * In the OAuth section of the page, select Create New Client ID. + * Set the Redirect URI to point to your auth handler + * Copy the "Client secret" and "Client ID" to the application settings as + ``{"google_oauth": {"key": CLIENT_ID, "secret": CLIENT_SECRET}}`` + + .. versionadded:: 3.2 + """ + + _OAUTH_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth" + _OAUTH_ACCESS_TOKEN_URL = "https://www.googleapis.com/oauth2/v4/token" + _OAUTH_USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo" + _OAUTH_NO_CALLBACKS = False + _OAUTH_SETTINGS_KEY = "google_oauth" + + async def get_authenticated_user( + self, redirect_uri: str, code: str + ) -> Dict[str, Any]: + """Handles the login for the Google user, returning an access token. + + The result is a dictionary containing an ``access_token`` field + ([among others](https://developers.google.com/identity/protocols/OAuth2WebServer#handlingtheresponse)). + Unlike other ``get_authenticated_user`` methods in this package, + this method does not return any additional information about the user. + The returned access token can be used with `OAuth2Mixin.oauth2_request` + to request additional information (perhaps from + ``https://www.googleapis.com/oauth2/v2/userinfo``) + + Example usage: + + .. testcode:: + + class GoogleOAuth2LoginHandler(tornado.web.RequestHandler, + tornado.auth.GoogleOAuth2Mixin): + async def get(self): + if self.get_argument('code', False): + access = await self.get_authenticated_user( + redirect_uri='http://your.site.com/auth/google', + code=self.get_argument('code')) + user = await self.oauth2_request( + "https://www.googleapis.com/oauth2/v1/userinfo", + access_token=access["access_token"]) + # Save the user and access token with + # e.g. set_secure_cookie. + else: + await self.authorize_redirect( + redirect_uri='http://your.site.com/auth/google', + client_id=self.settings['google_oauth']['key'], + scope=['profile', 'email'], + response_type='code', + extra_params={'approval_prompt': 'auto'}) + + .. testoutput:: + :hide: + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned awaitable object instead. + """ # noqa: E501 + handler = cast(RequestHandler, self) + http = self.get_auth_http_client() + body = urllib.parse.urlencode( + { + "redirect_uri": redirect_uri, + "code": code, + "client_id": handler.settings[self._OAUTH_SETTINGS_KEY]["key"], + "client_secret": handler.settings[self._OAUTH_SETTINGS_KEY]["secret"], + "grant_type": "authorization_code", + } + ) + + response = await http.fetch( + self._OAUTH_ACCESS_TOKEN_URL, + method="POST", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + body=body, + ) + return escape.json_decode(response.body) + + +class FacebookGraphMixin(OAuth2Mixin): + """Facebook authentication using the new Graph API and OAuth2.""" + + _OAUTH_ACCESS_TOKEN_URL = "https://graph.facebook.com/oauth/access_token?" + _OAUTH_AUTHORIZE_URL = "https://www.facebook.com/dialog/oauth?" + _OAUTH_NO_CALLBACKS = False + _FACEBOOK_BASE_URL = "https://graph.facebook.com" + + async def get_authenticated_user( + self, + redirect_uri: str, + client_id: str, + client_secret: str, + code: str, + extra_fields: Dict[str, Any] = None, + ) -> Optional[Dict[str, Any]]: + """Handles the login for the Facebook user, returning a user object. + + Example usage: + + .. testcode:: + + class FacebookGraphLoginHandler(tornado.web.RequestHandler, + tornado.auth.FacebookGraphMixin): + async def get(self): + if self.get_argument("code", False): + user = await self.get_authenticated_user( + redirect_uri='/auth/facebookgraph/', + client_id=self.settings["facebook_api_key"], + client_secret=self.settings["facebook_secret"], + code=self.get_argument("code")) + # Save the user with e.g. set_secure_cookie + else: + await self.authorize_redirect( + redirect_uri='/auth/facebookgraph/', + client_id=self.settings["facebook_api_key"], + extra_params={"scope": "read_stream,offline_access"}) + + .. testoutput:: + :hide: + + This method returns a dictionary which may contain the following fields: + + * ``access_token``, a string which may be passed to `facebook_request` + * ``session_expires``, an integer encoded as a string representing + the time until the access token expires in seconds. This field should + be used like ``int(user['session_expires'])``; in a future version of + Tornado it will change from a string to an integer. + * ``id``, ``name``, ``first_name``, ``last_name``, ``locale``, ``picture``, + ``link``, plus any fields named in the ``extra_fields`` argument. These + fields are copied from the Facebook graph API + `user object <https://developers.facebook.com/docs/graph-api/reference/user>`_ + + .. versionchanged:: 4.5 + The ``session_expires`` field was updated to support changes made to the + Facebook API in March 2017. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned awaitable object instead. + """ + http = self.get_auth_http_client() + args = { + "redirect_uri": redirect_uri, + "code": code, + "client_id": client_id, + "client_secret": client_secret, + } + + fields = set( + ["id", "name", "first_name", "last_name", "locale", "picture", "link"] + ) + if extra_fields: + fields.update(extra_fields) + + response = await http.fetch( + self._oauth_request_token_url(**args) # type: ignore + ) + args = escape.json_decode(response.body) + session = { + "access_token": args.get("access_token"), + "expires_in": args.get("expires_in"), + } + assert session["access_token"] is not None + + user = await self.facebook_request( + path="/me", + access_token=session["access_token"], + appsecret_proof=hmac.new( + key=client_secret.encode("utf8"), + msg=session["access_token"].encode("utf8"), + digestmod=hashlib.sha256, + ).hexdigest(), + fields=",".join(fields), + ) + + if user is None: + return None + + fieldmap = {} + for field in fields: + fieldmap[field] = user.get(field) + + # session_expires is converted to str for compatibility with + # older versions in which the server used url-encoding and + # this code simply returned the string verbatim. + # This should change in Tornado 5.0. + fieldmap.update( + { + "access_token": session["access_token"], + "session_expires": str(session.get("expires_in")), + } + ) + return fieldmap + + async def facebook_request( + self, + path: str, + access_token: str = None, + post_args: Dict[str, Any] = None, + **args: Any + ) -> Any: + """Fetches the given relative API path, e.g., "/btaylor/picture" + + If the request is a POST, ``post_args`` should be provided. Query + string arguments should be given as keyword arguments. + + An introduction to the Facebook Graph API can be found at + http://developers.facebook.com/docs/api + + Many methods require an OAuth access token which you can + obtain through `~OAuth2Mixin.authorize_redirect` and + `get_authenticated_user`. The user returned through that + process includes an ``access_token`` attribute that can be + used to make authenticated requests via this method. + + Example usage: + + .. testcode:: + + class MainHandler(tornado.web.RequestHandler, + tornado.auth.FacebookGraphMixin): + @tornado.web.authenticated + async def get(self): + new_entry = await self.facebook_request( + "/me/feed", + post_args={"message": "I am posting from my Tornado application!"}, + access_token=self.current_user["access_token"]) + + if not new_entry: + # Call failed; perhaps missing permission? + yield self.authorize_redirect() + return + self.finish("Posted a message!") + + .. testoutput:: + :hide: + + The given path is relative to ``self._FACEBOOK_BASE_URL``, + by default "https://graph.facebook.com". + + This method is a wrapper around `OAuth2Mixin.oauth2_request`; + the only difference is that this method takes a relative path, + while ``oauth2_request`` takes a complete url. + + .. versionchanged:: 3.1 + Added the ability to override ``self._FACEBOOK_BASE_URL``. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned awaitable object instead. + """ + url = self._FACEBOOK_BASE_URL + path + return await self.oauth2_request( + url, access_token=access_token, post_args=post_args, **args + ) + + +def _oauth_signature( + consumer_token: Dict[str, Any], + method: str, + url: str, + parameters: Dict[str, Any] = {}, + token: Dict[str, Any] = None, +) -> bytes: + """Calculates the HMAC-SHA1 OAuth signature for the given request. + + See http://oauth.net/core/1.0/#signing_process + """ + parts = urllib.parse.urlparse(url) + scheme, netloc, path = parts[:3] + normalized_url = scheme.lower() + "://" + netloc.lower() + path + + base_elems = [] + base_elems.append(method.upper()) + base_elems.append(normalized_url) + base_elems.append( + "&".join( + "%s=%s" % (k, _oauth_escape(str(v))) for k, v in sorted(parameters.items()) + ) + ) + base_string = "&".join(_oauth_escape(e) for e in base_elems) + + key_elems = [escape.utf8(consumer_token["secret"])] + key_elems.append(escape.utf8(token["secret"] if token else "")) + key = b"&".join(key_elems) + + hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1) + return binascii.b2a_base64(hash.digest())[:-1] + + +def _oauth10a_signature( + consumer_token: Dict[str, Any], + method: str, + url: str, + parameters: Dict[str, Any] = {}, + token: Dict[str, Any] = None, +) -> bytes: + """Calculates the HMAC-SHA1 OAuth 1.0a signature for the given request. + + See http://oauth.net/core/1.0a/#signing_process + """ + parts = urllib.parse.urlparse(url) + scheme, netloc, path = parts[:3] + normalized_url = scheme.lower() + "://" + netloc.lower() + path + + base_elems = [] + base_elems.append(method.upper()) + base_elems.append(normalized_url) + base_elems.append( + "&".join( + "%s=%s" % (k, _oauth_escape(str(v))) for k, v in sorted(parameters.items()) + ) + ) + + base_string = "&".join(_oauth_escape(e) for e in base_elems) + key_elems = [escape.utf8(urllib.parse.quote(consumer_token["secret"], safe="~"))] + key_elems.append( + escape.utf8(urllib.parse.quote(token["secret"], safe="~") if token else "") + ) + key = b"&".join(key_elems) + + hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1) + return binascii.b2a_base64(hash.digest())[:-1] + + +def _oauth_escape(val: Union[str, bytes]) -> str: + if isinstance(val, unicode_type): + val = val.encode("utf-8") + return urllib.parse.quote(val, safe="~") + + +def _oauth_parse_response(body: bytes) -> Dict[str, Any]: + # I can't find an officially-defined encoding for oauth responses and + # have never seen anyone use non-ascii. Leave the response in a byte + # string for python 2, and use utf8 on python 3. + body_str = escape.native_str(body) + p = urllib.parse.parse_qs(body_str, keep_blank_values=False) + token = dict(key=p["oauth_token"][0], secret=p["oauth_token_secret"][0]) + + # Add the extra parameters the Provider included to the token + special = ("oauth_token", "oauth_token_secret") + token.update((k, p[k][0]) for k in p if k not in special) + return token diff --git a/venv/lib/python3.7/site-packages/tornado/autoreload.py b/venv/lib/python3.7/site-packages/tornado/autoreload.py new file mode 100644 index 0000000..1c47aae --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/autoreload.py @@ -0,0 +1,364 @@ +# +# Copyright 2009 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. + +"""Automatically restart the server when a source file is modified. + +Most applications should not access this module directly. Instead, +pass the keyword argument ``autoreload=True`` to the +`tornado.web.Application` constructor (or ``debug=True``, which +enables this setting and several others). This will enable autoreload +mode as well as checking for changes to templates and static +resources. Note that restarting is a destructive operation and any +requests in progress will be aborted when the process restarts. (If +you want to disable autoreload while using other debug-mode features, +pass both ``debug=True`` and ``autoreload=False``). + +This module can also be used as a command-line wrapper around scripts +such as unit test runners. See the `main` method for details. + +The command-line wrapper and Application debug modes can be used together. +This combination is encouraged as the wrapper catches syntax errors and +other import-time failures, while debug mode catches changes once +the server has started. + +This module will not work correctly when `.HTTPServer`'s multi-process +mode is used. + +Reloading loses any Python interpreter command-line arguments (e.g. ``-u``) +because it re-executes Python using ``sys.executable`` and ``sys.argv``. +Additionally, modifying these variables will cause reloading to behave +incorrectly. + +""" + +import os +import sys + +# sys.path handling +# ----------------- +# +# If a module is run with "python -m", the current directory (i.e. "") +# is automatically prepended to sys.path, but not if it is run as +# "path/to/file.py". The processing for "-m" rewrites the former to +# the latter, so subsequent executions won't have the same path as the +# original. +# +# Conversely, when run as path/to/file.py, the directory containing +# file.py gets added to the path, which can cause confusion as imports +# may become relative in spite of the future import. +# +# We address the former problem by reconstructing the original command +# line (Python >= 3.4) or by setting the $PYTHONPATH environment +# variable (Python < 3.4) before re-execution so the new process will +# see the correct path. We attempt to address the latter problem when +# tornado.autoreload is run as __main__. + +if __name__ == "__main__": + # This sys.path manipulation must come before our imports (as much + # as possible - if we introduced a tornado.sys or tornado.os + # module we'd be in trouble), or else our imports would become + # relative again despite the future import. + # + # There is a separate __main__ block at the end of the file to call main(). + if sys.path[0] == os.path.dirname(__file__): + del sys.path[0] + +import functools +import logging +import os +import pkgutil # type: ignore +import sys +import traceback +import types +import subprocess +import weakref + +from tornado import ioloop +from tornado.log import gen_log +from tornado import process +from tornado.util import exec_in + +try: + import signal +except ImportError: + signal = None # type: ignore + +import typing +from typing import Callable, Dict + +if typing.TYPE_CHECKING: + from typing import List, Optional, Union # noqa: F401 + +# os.execv is broken on Windows and can't properly parse command line +# arguments and executable name if they contain whitespaces. subprocess +# fixes that behavior. +_has_execv = sys.platform != "win32" + +_watched_files = set() +_reload_hooks = [] +_reload_attempted = False +_io_loops = weakref.WeakKeyDictionary() # type: ignore +_autoreload_is_main = False +_original_argv = None # type: Optional[List[str]] +_original_spec = None + + +def start(check_time: int = 500) -> None: + """Begins watching source files for changes. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + """ + io_loop = ioloop.IOLoop.current() + if io_loop in _io_loops: + return + _io_loops[io_loop] = True + if len(_io_loops) > 1: + gen_log.warning("tornado.autoreload started more than once in the same process") + modify_times = {} # type: Dict[str, float] + callback = functools.partial(_reload_on_update, modify_times) + scheduler = ioloop.PeriodicCallback(callback, check_time) + scheduler.start() + + +def wait() -> None: + """Wait for a watched file to change, then restart the process. + + Intended to be used at the end of scripts like unit test runners, + to run the tests again after any source file changes (but see also + the command-line interface in `main`) + """ + io_loop = ioloop.IOLoop() + io_loop.add_callback(start) + io_loop.start() + + +def watch(filename: str) -> None: + """Add a file to the watch list. + + All imported modules are watched by default. + """ + _watched_files.add(filename) + + +def add_reload_hook(fn: Callable[[], None]) -> None: + """Add a function to be called before reloading the process. + + Note that for open file and socket handles it is generally + preferable to set the ``FD_CLOEXEC`` flag (using `fcntl` or + ``tornado.platform.auto.set_close_exec``) instead + of using a reload hook to close them. + """ + _reload_hooks.append(fn) + + +def _reload_on_update(modify_times: Dict[str, float]) -> None: + if _reload_attempted: + # We already tried to reload and it didn't work, so don't try again. + return + if process.task_id() is not None: + # We're in a child process created by fork_processes. If child + # processes restarted themselves, they'd all restart and then + # all call fork_processes again. + return + for module in list(sys.modules.values()): + # Some modules play games with sys.modules (e.g. email/__init__.py + # in the standard library), and occasionally this can cause strange + # failures in getattr. Just ignore anything that's not an ordinary + # module. + if not isinstance(module, types.ModuleType): + continue + path = getattr(module, "__file__", None) + if not path: + continue + if path.endswith(".pyc") or path.endswith(".pyo"): + path = path[:-1] + _check_file(modify_times, path) + for path in _watched_files: + _check_file(modify_times, path) + + +def _check_file(modify_times: Dict[str, float], path: str) -> None: + try: + modified = os.stat(path).st_mtime + except Exception: + return + if path not in modify_times: + modify_times[path] = modified + return + if modify_times[path] != modified: + gen_log.info("%s modified; restarting server", path) + _reload() + + +def _reload() -> None: + global _reload_attempted + _reload_attempted = True + for fn in _reload_hooks: + fn() + if hasattr(signal, "setitimer"): + # Clear the alarm signal set by + # ioloop.set_blocking_log_threshold so it doesn't fire + # after the exec. + signal.setitimer(signal.ITIMER_REAL, 0, 0) + # sys.path fixes: see comments at top of file. If __main__.__spec__ + # exists, we were invoked with -m and the effective path is about to + # change on re-exec. Reconstruct the original command line to + # ensure that the new process sees the same path we did. If + # __spec__ is not available (Python < 3.4), check instead if + # sys.path[0] is an empty string and add the current directory to + # $PYTHONPATH. + if _autoreload_is_main: + assert _original_argv is not None + spec = _original_spec + argv = _original_argv + else: + spec = getattr(sys.modules["__main__"], "__spec__", None) + argv = sys.argv + if spec: + argv = ["-m", spec.name] + argv[1:] + else: + path_prefix = "." + os.pathsep + if sys.path[0] == "" and not os.environ.get("PYTHONPATH", "").startswith( + path_prefix + ): + os.environ["PYTHONPATH"] = path_prefix + os.environ.get("PYTHONPATH", "") + if not _has_execv: + subprocess.Popen([sys.executable] + argv) + os._exit(0) + else: + try: + os.execv(sys.executable, [sys.executable] + argv) + except OSError: + # Mac OS X versions prior to 10.6 do not support execv in + # a process that contains multiple threads. Instead of + # re-executing in the current process, start a new one + # and cause the current process to exit. This isn't + # ideal since the new process is detached from the parent + # terminal and thus cannot easily be killed with ctrl-C, + # but it's better than not being able to autoreload at + # all. + # Unfortunately the errno returned in this case does not + # appear to be consistent, so we can't easily check for + # this error specifically. + os.spawnv( # type: ignore + os.P_NOWAIT, sys.executable, [sys.executable] + argv + ) + # At this point the IOLoop has been closed and finally + # blocks will experience errors if we allow the stack to + # unwind, so just exit uncleanly. + os._exit(0) + + +_USAGE = """\ +Usage: + python -m tornado.autoreload -m module.to.run [args...] + python -m tornado.autoreload path/to/script.py [args...] +""" + + +def main() -> None: + """Command-line wrapper to re-run a script whenever its source changes. + + Scripts may be specified by filename or module name:: + + python -m tornado.autoreload -m tornado.test.runtests + python -m tornado.autoreload tornado/test/runtests.py + + Running a script with this wrapper is similar to calling + `tornado.autoreload.wait` at the end of the script, but this wrapper + can catch import-time problems like syntax errors that would otherwise + prevent the script from reaching its call to `wait`. + """ + # Remember that we were launched with autoreload as main. + # The main module can be tricky; set the variables both in our globals + # (which may be __main__) and the real importable version. + import tornado.autoreload + + global _autoreload_is_main + global _original_argv, _original_spec + tornado.autoreload._autoreload_is_main = _autoreload_is_main = True + original_argv = sys.argv + tornado.autoreload._original_argv = _original_argv = original_argv + original_spec = getattr(sys.modules["__main__"], "__spec__", None) + tornado.autoreload._original_spec = _original_spec = original_spec + sys.argv = sys.argv[:] + if len(sys.argv) >= 3 and sys.argv[1] == "-m": + mode = "module" + module = sys.argv[2] + del sys.argv[1:3] + elif len(sys.argv) >= 2: + mode = "script" + script = sys.argv[1] + sys.argv = sys.argv[1:] + else: + print(_USAGE, file=sys.stderr) + sys.exit(1) + + try: + if mode == "module": + import runpy + + runpy.run_module(module, run_name="__main__", alter_sys=True) + elif mode == "script": + with open(script) as f: + # Execute the script in our namespace instead of creating + # a new one so that something that tries to import __main__ + # (e.g. the unittest module) will see names defined in the + # script instead of just those defined in this module. + global __file__ + __file__ = script + # If __package__ is defined, imports may be incorrectly + # interpreted as relative to this module. + global __package__ + del __package__ + exec_in(f.read(), globals(), globals()) + except SystemExit as e: + logging.basicConfig() + gen_log.info("Script exited with status %s", e.code) + except Exception as e: + logging.basicConfig() + gen_log.warning("Script exited with uncaught exception", exc_info=True) + # If an exception occurred at import time, the file with the error + # never made it into sys.modules and so we won't know to watch it. + # Just to make sure we've covered everything, walk the stack trace + # from the exception and watch every file. + for (filename, lineno, name, line) in traceback.extract_tb(sys.exc_info()[2]): + watch(filename) + if isinstance(e, SyntaxError): + # SyntaxErrors are special: their innermost stack frame is fake + # so extract_tb won't see it and we have to get the filename + # from the exception object. + watch(e.filename) + else: + logging.basicConfig() + gen_log.info("Script exited normally") + # restore sys.argv so subsequent executions will include autoreload + sys.argv = original_argv + + if mode == "module": + # runpy did a fake import of the module as __main__, but now it's + # no longer in sys.modules. Figure out where it is and watch it. + loader = pkgutil.get_loader(module) + if loader is not None: + watch(loader.get_filename()) # type: ignore + + wait() + + +if __name__ == "__main__": + # See also the other __main__ block at the top of the file, which modifies + # sys.path before our imports + main() diff --git a/venv/lib/python3.7/site-packages/tornado/concurrent.py b/venv/lib/python3.7/site-packages/tornado/concurrent.py new file mode 100644 index 0000000..3a49940 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/concurrent.py @@ -0,0 +1,264 @@ +# +# 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. +"""Utilities for working with ``Future`` objects. + +Tornado previously provided its own ``Future`` class, but now uses +`asyncio.Future`. This module contains utility functions for working +with `asyncio.Future` in a way that is backwards-compatible with +Tornado's old ``Future`` implementation. + +While this module is an important part of Tornado's internal +implementation, applications rarely need to interact with it +directly. + +""" + +import asyncio +from concurrent import futures +import functools +import sys +import types + +from tornado.log import app_log + +import typing +from typing import Any, Callable, Optional, Tuple, Union + +_T = typing.TypeVar("_T") + + +class ReturnValueIgnoredError(Exception): + # No longer used; was previously used by @return_future + pass + + +Future = asyncio.Future + +FUTURES = (futures.Future, Future) + + +def is_future(x: Any) -> bool: + return isinstance(x, FUTURES) + + +class DummyExecutor(futures.Executor): + def submit( + self, fn: Callable[..., _T], *args: Any, **kwargs: Any + ) -> "futures.Future[_T]": + future = futures.Future() # type: futures.Future[_T] + try: + future_set_result_unless_cancelled(future, fn(*args, **kwargs)) + except Exception: + future_set_exc_info(future, sys.exc_info()) + return future + + def shutdown(self, wait: bool = True) -> None: + pass + + +dummy_executor = DummyExecutor() + + +def run_on_executor(*args: Any, **kwargs: Any) -> Callable: + """Decorator to run a synchronous method asynchronously on an executor. + + The decorated method may be called with a ``callback`` keyword + argument and returns a future. + + The executor to be used is determined by the ``executor`` + attributes of ``self``. To use a different attribute name, pass a + keyword argument to the decorator:: + + @run_on_executor(executor='_thread_pool') + def foo(self): + pass + + This decorator should not be confused with the similarly-named + `.IOLoop.run_in_executor`. In general, using ``run_in_executor`` + when *calling* a blocking method is recommended instead of using + this decorator when *defining* a method. If compatibility with older + versions of Tornado is required, consider defining an executor + and using ``executor.submit()`` at the call site. + + .. versionchanged:: 4.2 + Added keyword arguments to use alternative attributes. + + .. versionchanged:: 5.0 + Always uses the current IOLoop instead of ``self.io_loop``. + + .. versionchanged:: 5.1 + Returns a `.Future` compatible with ``await`` instead of a + `concurrent.futures.Future`. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in + 6.0. The decorator itself is discouraged in new code but will + not be removed in 6.0. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. + """ + # Fully type-checking decorators is tricky, and this one is + # discouraged anyway so it doesn't have all the generic magic. + def run_on_executor_decorator(fn: Callable) -> Callable[..., Future]: + executor = kwargs.get("executor", "executor") + + @functools.wraps(fn) + def wrapper(self: Any, *args: Any, **kwargs: Any) -> Future: + async_future = Future() # type: Future + conc_future = getattr(self, executor).submit(fn, self, *args, **kwargs) + chain_future(conc_future, async_future) + return async_future + + return wrapper + + if args and kwargs: + raise ValueError("cannot combine positional and keyword args") + if len(args) == 1: + return run_on_executor_decorator(args[0]) + elif len(args) != 0: + raise ValueError("expected 1 argument, got %d", len(args)) + return run_on_executor_decorator + + +_NO_RESULT = object() + + +def chain_future(a: "Future[_T]", b: "Future[_T]") -> None: + """Chain two futures together so that when one completes, so does the other. + + The result (success or failure) of ``a`` will be copied to ``b``, unless + ``b`` has already been completed or cancelled by the time ``a`` finishes. + + .. versionchanged:: 5.0 + + Now accepts both Tornado/asyncio `Future` objects and + `concurrent.futures.Future`. + + """ + + def copy(future: "Future[_T]") -> None: + assert future is a + if b.done(): + return + if hasattr(a, "exc_info") and a.exc_info() is not None: # type: ignore + future_set_exc_info(b, a.exc_info()) # type: ignore + elif a.exception() is not None: + b.set_exception(a.exception()) + else: + b.set_result(a.result()) + + if isinstance(a, Future): + future_add_done_callback(a, copy) + else: + # concurrent.futures.Future + from tornado.ioloop import IOLoop + + IOLoop.current().add_future(a, copy) + + +def future_set_result_unless_cancelled( + future: "Union[futures.Future[_T], Future[_T]]", value: _T +) -> None: + """Set the given ``value`` as the `Future`'s result, if not cancelled. + + Avoids ``asyncio.InvalidStateError`` when calling ``set_result()`` on + a cancelled `asyncio.Future`. + + .. versionadded:: 5.0 + """ + if not future.cancelled(): + future.set_result(value) + + +def future_set_exception_unless_cancelled( + future: "Union[futures.Future[_T], Future[_T]]", exc: BaseException +) -> None: + """Set the given ``exc`` as the `Future`'s exception. + + If the Future is already canceled, logs the exception instead. If + this logging is not desired, the caller should explicitly check + the state of the Future and call ``Future.set_exception`` instead of + this wrapper. + + Avoids ``asyncio.InvalidStateError`` when calling ``set_exception()`` on + a cancelled `asyncio.Future`. + + .. versionadded:: 6.0 + + """ + if not future.cancelled(): + future.set_exception(exc) + else: + app_log.error("Exception after Future was cancelled", exc_info=exc) + + +def future_set_exc_info( + future: "Union[futures.Future[_T], Future[_T]]", + exc_info: Tuple[ + Optional[type], Optional[BaseException], Optional[types.TracebackType] + ], +) -> None: + """Set the given ``exc_info`` as the `Future`'s exception. + + Understands both `asyncio.Future` and the extensions in older + versions of Tornado to enable better tracebacks on Python 2. + + .. versionadded:: 5.0 + + .. versionchanged:: 6.0 + + If the future is already cancelled, this function is a no-op. + (previously ``asyncio.InvalidStateError`` would be raised) + + """ + if exc_info[1] is None: + raise Exception("future_set_exc_info called with no exception") + future_set_exception_unless_cancelled(future, exc_info[1]) + + +@typing.overload +def future_add_done_callback( + future: "futures.Future[_T]", callback: Callable[["futures.Future[_T]"], None] +) -> None: + pass + + +@typing.overload # noqa: F811 +def future_add_done_callback( + future: "Future[_T]", callback: Callable[["Future[_T]"], None] +) -> None: + pass + + +def future_add_done_callback( # noqa: F811 + future: "Union[futures.Future[_T], Future[_T]]", callback: Callable[..., None] +) -> None: + """Arrange to call ``callback`` when ``future`` is complete. + + ``callback`` is invoked with one argument, the ``future``. + + If ``future`` is already done, ``callback`` is invoked immediately. + This may differ from the behavior of ``Future.add_done_callback``, + which makes no such guarantee. + + .. versionadded:: 5.0 + """ + if future.done(): + callback(future) + else: + future.add_done_callback(callback) diff --git a/venv/lib/python3.7/site-packages/tornado/curl_httpclient.py b/venv/lib/python3.7/site-packages/tornado/curl_httpclient.py new file mode 100644 index 0000000..4119585 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/curl_httpclient.py @@ -0,0 +1,572 @@ +# +# Copyright 2009 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. + +"""Non-blocking HTTP client implementation using pycurl.""" + +import collections +import functools +import logging +import pycurl # type: ignore +import threading +import time +from io import BytesIO + +from tornado import httputil +from tornado import ioloop + +from tornado.escape import utf8, native_str +from tornado.httpclient import ( + HTTPRequest, + HTTPResponse, + HTTPError, + AsyncHTTPClient, + main, +) +from tornado.log import app_log + +from typing import Dict, Any, Callable, Union +import typing + +if typing.TYPE_CHECKING: + from typing import Deque, Tuple, Optional # noqa: F401 + +curl_log = logging.getLogger("tornado.curl_httpclient") + + +class CurlAsyncHTTPClient(AsyncHTTPClient): + def initialize( # type: ignore + self, max_clients: int = 10, defaults: Dict[str, Any] = None + ) -> None: + super(CurlAsyncHTTPClient, self).initialize(defaults=defaults) + self._multi = pycurl.CurlMulti() + self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout) + self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket) + self._curls = [self._curl_create() for i in range(max_clients)] + self._free_list = self._curls[:] + self._requests = ( + collections.deque() + ) # type: Deque[Tuple[HTTPRequest, Callable[[HTTPResponse], None], float]] + self._fds = {} # type: Dict[int, int] + self._timeout = None # type: Optional[object] + + # libcurl has bugs that sometimes cause it to not report all + # relevant file descriptors and timeouts to TIMERFUNCTION/ + # SOCKETFUNCTION. Mitigate the effects of such bugs by + # forcing a periodic scan of all active requests. + self._force_timeout_callback = ioloop.PeriodicCallback( + self._handle_force_timeout, 1000 + ) + self._force_timeout_callback.start() + + # Work around a bug in libcurl 7.29.0: Some fields in the curl + # multi object are initialized lazily, and its destructor will + # segfault if it is destroyed without having been used. Add + # and remove a dummy handle to make sure everything is + # initialized. + dummy_curl_handle = pycurl.Curl() + self._multi.add_handle(dummy_curl_handle) + self._multi.remove_handle(dummy_curl_handle) + + def close(self) -> None: + self._force_timeout_callback.stop() + if self._timeout is not None: + self.io_loop.remove_timeout(self._timeout) + for curl in self._curls: + curl.close() + self._multi.close() + super(CurlAsyncHTTPClient, self).close() + + # Set below properties to None to reduce the reference count of current + # instance, because those properties hold some methods of current + # instance that will case circular reference. + self._force_timeout_callback = None # type: ignore + self._multi = None + + def fetch_impl( + self, request: HTTPRequest, callback: Callable[[HTTPResponse], None] + ) -> None: + self._requests.append((request, callback, self.io_loop.time())) + self._process_queue() + self._set_timeout(0) + + def _handle_socket(self, event: int, fd: int, multi: Any, data: bytes) -> None: + """Called by libcurl when it wants to change the file descriptors + it cares about. + """ + event_map = { + pycurl.POLL_NONE: ioloop.IOLoop.NONE, + pycurl.POLL_IN: ioloop.IOLoop.READ, + pycurl.POLL_OUT: ioloop.IOLoop.WRITE, + pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE, + } + if event == pycurl.POLL_REMOVE: + if fd in self._fds: + self.io_loop.remove_handler(fd) + del self._fds[fd] + else: + ioloop_event = event_map[event] + # libcurl sometimes closes a socket and then opens a new + # one using the same FD without giving us a POLL_NONE in + # between. This is a problem with the epoll IOLoop, + # because the kernel can tell when a socket is closed and + # removes it from the epoll automatically, causing future + # update_handler calls to fail. Since we can't tell when + # this has happened, always use remove and re-add + # instead of update. + if fd in self._fds: + self.io_loop.remove_handler(fd) + self.io_loop.add_handler(fd, self._handle_events, ioloop_event) + self._fds[fd] = ioloop_event + + def _set_timeout(self, msecs: int) -> None: + """Called by libcurl to schedule a timeout.""" + if self._timeout is not None: + self.io_loop.remove_timeout(self._timeout) + self._timeout = self.io_loop.add_timeout( + self.io_loop.time() + msecs / 1000.0, self._handle_timeout + ) + + def _handle_events(self, fd: int, events: int) -> None: + """Called by IOLoop when there is activity on one of our + file descriptors. + """ + action = 0 + if events & ioloop.IOLoop.READ: + action |= pycurl.CSELECT_IN + if events & ioloop.IOLoop.WRITE: + action |= pycurl.CSELECT_OUT + while True: + try: + ret, num_handles = self._multi.socket_action(fd, action) + except pycurl.error as e: + ret = e.args[0] + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + self._finish_pending_requests() + + def _handle_timeout(self) -> None: + """Called by IOLoop when the requested timeout has passed.""" + self._timeout = None + while True: + try: + ret, num_handles = self._multi.socket_action(pycurl.SOCKET_TIMEOUT, 0) + except pycurl.error as e: + ret = e.args[0] + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + self._finish_pending_requests() + + # In theory, we shouldn't have to do this because curl will + # call _set_timeout whenever the timeout changes. However, + # sometimes after _handle_timeout we will need to reschedule + # immediately even though nothing has changed from curl's + # perspective. This is because when socket_action is + # called with SOCKET_TIMEOUT, libcurl decides internally which + # timeouts need to be processed by using a monotonic clock + # (where available) while tornado uses python's time.time() + # to decide when timeouts have occurred. When those clocks + # disagree on elapsed time (as they will whenever there is an + # NTP adjustment), tornado might call _handle_timeout before + # libcurl is ready. After each timeout, resync the scheduled + # timeout with libcurl's current state. + new_timeout = self._multi.timeout() + if new_timeout >= 0: + self._set_timeout(new_timeout) + + def _handle_force_timeout(self) -> None: + """Called by IOLoop periodically to ask libcurl to process any + events it may have forgotten about. + """ + while True: + try: + ret, num_handles = self._multi.socket_all() + except pycurl.error as e: + ret = e.args[0] + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + self._finish_pending_requests() + + def _finish_pending_requests(self) -> None: + """Process any requests that were completed by the last + call to multi.socket_action. + """ + while True: + num_q, ok_list, err_list = self._multi.info_read() + for curl in ok_list: + self._finish(curl) + for curl, errnum, errmsg in err_list: + self._finish(curl, errnum, errmsg) + if num_q == 0: + break + self._process_queue() + + def _process_queue(self) -> None: + while True: + started = 0 + while self._free_list and self._requests: + started += 1 + curl = self._free_list.pop() + (request, callback, queue_start_time) = self._requests.popleft() + curl.info = { + "headers": httputil.HTTPHeaders(), + "buffer": BytesIO(), + "request": request, + "callback": callback, + "queue_start_time": queue_start_time, + "curl_start_time": time.time(), + "curl_start_ioloop_time": self.io_loop.current().time(), + } + try: + self._curl_setup_request( + curl, request, curl.info["buffer"], curl.info["headers"] + ) + except Exception as e: + # If there was an error in setup, pass it on + # to the callback. Note that allowing the + # error to escape here will appear to work + # most of the time since we are still in the + # caller's original stack frame, but when + # _process_queue() is called from + # _finish_pending_requests the exceptions have + # nowhere to go. + self._free_list.append(curl) + callback(HTTPResponse(request=request, code=599, error=e)) + else: + self._multi.add_handle(curl) + + if not started: + break + + def _finish( + self, curl: pycurl.Curl, curl_error: int = None, curl_message: str = None + ) -> None: + info = curl.info + curl.info = None + self._multi.remove_handle(curl) + self._free_list.append(curl) + buffer = info["buffer"] + if curl_error: + assert curl_message is not None + error = CurlError(curl_error, curl_message) # type: Optional[CurlError] + assert error is not None + code = error.code + effective_url = None + buffer.close() + buffer = None + else: + error = None + code = curl.getinfo(pycurl.HTTP_CODE) + effective_url = curl.getinfo(pycurl.EFFECTIVE_URL) + buffer.seek(0) + # the various curl timings are documented at + # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html + time_info = dict( + queue=info["curl_start_ioloop_time"] - info["queue_start_time"], + namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME), + connect=curl.getinfo(pycurl.CONNECT_TIME), + appconnect=curl.getinfo(pycurl.APPCONNECT_TIME), + pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME), + starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME), + total=curl.getinfo(pycurl.TOTAL_TIME), + redirect=curl.getinfo(pycurl.REDIRECT_TIME), + ) + try: + info["callback"]( + HTTPResponse( + request=info["request"], + code=code, + headers=info["headers"], + buffer=buffer, + effective_url=effective_url, + error=error, + reason=info["headers"].get("X-Http-Reason", None), + request_time=self.io_loop.time() - info["curl_start_ioloop_time"], + start_time=info["curl_start_time"], + time_info=time_info, + ) + ) + except Exception: + self.handle_callback_exception(info["callback"]) + + def handle_callback_exception(self, callback: Any) -> None: + app_log.error("Exception in callback %r", callback, exc_info=True) + + def _curl_create(self) -> pycurl.Curl: + curl = pycurl.Curl() + if curl_log.isEnabledFor(logging.DEBUG): + curl.setopt(pycurl.VERBOSE, 1) + curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug) + if hasattr( + pycurl, "PROTOCOLS" + ): # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12) + curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS) + curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS) + return curl + + def _curl_setup_request( + self, + curl: pycurl.Curl, + request: HTTPRequest, + buffer: BytesIO, + headers: httputil.HTTPHeaders, + ) -> None: + curl.setopt(pycurl.URL, native_str(request.url)) + + # libcurl's magic "Expect: 100-continue" behavior causes delays + # with servers that don't support it (which include, among others, + # Google's OpenID endpoint). Additionally, this behavior has + # a bug in conjunction with the curl_multi_socket_action API + # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976), + # which increases the delays. It's more trouble than it's worth, + # so just turn off the feature (yes, setting Expect: to an empty + # value is the official way to disable this) + if "Expect" not in request.headers: + request.headers["Expect"] = "" + + # libcurl adds Pragma: no-cache by default; disable that too + if "Pragma" not in request.headers: + request.headers["Pragma"] = "" + + curl.setopt( + pycurl.HTTPHEADER, + [ + "%s: %s" % (native_str(k), native_str(v)) + for k, v in request.headers.get_all() + ], + ) + + curl.setopt( + pycurl.HEADERFUNCTION, + functools.partial( + self._curl_header_callback, headers, request.header_callback + ), + ) + if request.streaming_callback: + + def write_function(b: Union[bytes, bytearray]) -> int: + assert request.streaming_callback is not None + self.io_loop.add_callback(request.streaming_callback, b) + return len(b) + + else: + write_function = buffer.write + curl.setopt(pycurl.WRITEFUNCTION, write_function) + curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects) + curl.setopt(pycurl.MAXREDIRS, request.max_redirects) + assert request.connect_timeout is not None + curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout)) + assert request.request_timeout is not None + curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout)) + if request.user_agent: + curl.setopt(pycurl.USERAGENT, native_str(request.user_agent)) + else: + curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)") + if request.network_interface: + curl.setopt(pycurl.INTERFACE, request.network_interface) + if request.decompress_response: + curl.setopt(pycurl.ENCODING, "gzip,deflate") + else: + curl.setopt(pycurl.ENCODING, "none") + if request.proxy_host and request.proxy_port: + curl.setopt(pycurl.PROXY, request.proxy_host) + curl.setopt(pycurl.PROXYPORT, request.proxy_port) + if request.proxy_username: + assert request.proxy_password is not None + credentials = httputil.encode_username_password( + request.proxy_username, request.proxy_password + ) + curl.setopt(pycurl.PROXYUSERPWD, credentials) + + if request.proxy_auth_mode is None or request.proxy_auth_mode == "basic": + curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC) + elif request.proxy_auth_mode == "digest": + curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST) + else: + raise ValueError( + "Unsupported proxy_auth_mode %s" % request.proxy_auth_mode + ) + else: + curl.setopt(pycurl.PROXY, "") + curl.unsetopt(pycurl.PROXYUSERPWD) + if request.validate_cert: + curl.setopt(pycurl.SSL_VERIFYPEER, 1) + curl.setopt(pycurl.SSL_VERIFYHOST, 2) + else: + curl.setopt(pycurl.SSL_VERIFYPEER, 0) + curl.setopt(pycurl.SSL_VERIFYHOST, 0) + if request.ca_certs is not None: + curl.setopt(pycurl.CAINFO, request.ca_certs) + else: + # There is no way to restore pycurl.CAINFO to its default value + # (Using unsetopt makes it reject all certificates). + # I don't see any way to read the default value from python so it + # can be restored later. We'll have to just leave CAINFO untouched + # if no ca_certs file was specified, and require that if any + # request uses a custom ca_certs file, they all must. + pass + + if request.allow_ipv6 is False: + # Curl behaves reasonably when DNS resolution gives an ipv6 address + # that we can't reach, so allow ipv6 unless the user asks to disable. + curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) + else: + curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER) + + # Set the request method through curl's irritating interface which makes + # up names for almost every single method + curl_options = { + "GET": pycurl.HTTPGET, + "POST": pycurl.POST, + "PUT": pycurl.UPLOAD, + "HEAD": pycurl.NOBODY, + } + custom_methods = set(["DELETE", "OPTIONS", "PATCH"]) + for o in curl_options.values(): + curl.setopt(o, False) + if request.method in curl_options: + curl.unsetopt(pycurl.CUSTOMREQUEST) + curl.setopt(curl_options[request.method], True) + elif request.allow_nonstandard_methods or request.method in custom_methods: + curl.setopt(pycurl.CUSTOMREQUEST, request.method) + else: + raise KeyError("unknown method " + request.method) + + body_expected = request.method in ("POST", "PATCH", "PUT") + body_present = request.body is not None + if not request.allow_nonstandard_methods: + # Some HTTP methods nearly always have bodies while others + # almost never do. Fail in this case unless the user has + # opted out of sanity checks with allow_nonstandard_methods. + if (body_expected and not body_present) or ( + body_present and not body_expected + ): + raise ValueError( + "Body must %sbe None for method %s (unless " + "allow_nonstandard_methods is true)" + % ("not " if body_expected else "", request.method) + ) + + if body_expected or body_present: + if request.method == "GET": + # Even with `allow_nonstandard_methods` we disallow + # GET with a body (because libcurl doesn't allow it + # unless we use CUSTOMREQUEST). While the spec doesn't + # forbid clients from sending a body, it arguably + # disallows the server from doing anything with them. + raise ValueError("Body must be None for GET request") + request_buffer = BytesIO(utf8(request.body or "")) + + def ioctl(cmd: int) -> None: + if cmd == curl.IOCMD_RESTARTREAD: + request_buffer.seek(0) + + curl.setopt(pycurl.READFUNCTION, request_buffer.read) + curl.setopt(pycurl.IOCTLFUNCTION, ioctl) + if request.method == "POST": + curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or "")) + else: + curl.setopt(pycurl.UPLOAD, True) + curl.setopt(pycurl.INFILESIZE, len(request.body or "")) + + if request.auth_username is not None: + assert request.auth_password is not None + if request.auth_mode is None or request.auth_mode == "basic": + curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) + elif request.auth_mode == "digest": + curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST) + else: + raise ValueError("Unsupported auth_mode %s" % request.auth_mode) + + userpwd = httputil.encode_username_password( + request.auth_username, request.auth_password + ) + curl.setopt(pycurl.USERPWD, userpwd) + curl_log.debug( + "%s %s (username: %r)", + request.method, + request.url, + request.auth_username, + ) + else: + curl.unsetopt(pycurl.USERPWD) + curl_log.debug("%s %s", request.method, request.url) + + if request.client_cert is not None: + curl.setopt(pycurl.SSLCERT, request.client_cert) + + if request.client_key is not None: + curl.setopt(pycurl.SSLKEY, request.client_key) + + if request.ssl_options is not None: + raise ValueError("ssl_options not supported in curl_httpclient") + + if threading.active_count() > 1: + # libcurl/pycurl is not thread-safe by default. When multiple threads + # are used, signals should be disabled. This has the side effect + # of disabling DNS timeouts in some environments (when libcurl is + # not linked against ares), so we don't do it when there is only one + # thread. Applications that use many short-lived threads may need + # to set NOSIGNAL manually in a prepare_curl_callback since + # there may not be any other threads running at the time we call + # threading.activeCount. + curl.setopt(pycurl.NOSIGNAL, 1) + if request.prepare_curl_callback is not None: + request.prepare_curl_callback(curl) + + def _curl_header_callback( + self, + headers: httputil.HTTPHeaders, + header_callback: Callable[[str], None], + header_line_bytes: bytes, + ) -> None: + header_line = native_str(header_line_bytes.decode("latin1")) + if header_callback is not None: + self.io_loop.add_callback(header_callback, header_line) + # header_line as returned by curl includes the end-of-line characters. + # whitespace at the start should be preserved to allow multi-line headers + header_line = header_line.rstrip() + if header_line.startswith("HTTP/"): + headers.clear() + try: + (__, __, reason) = httputil.parse_response_start_line(header_line) + header_line = "X-Http-Reason: %s" % reason + except httputil.HTTPInputError: + return + if not header_line: + return + headers.parse_line(header_line) + + def _curl_debug(self, debug_type: int, debug_msg: str) -> None: + debug_types = ("I", "<", ">", "<", ">") + if debug_type == 0: + debug_msg = native_str(debug_msg) + curl_log.debug("%s", debug_msg.strip()) + elif debug_type in (1, 2): + debug_msg = native_str(debug_msg) + for line in debug_msg.splitlines(): + curl_log.debug("%s %s", debug_types[debug_type], line) + elif debug_type == 4: + curl_log.debug("%s %r", debug_types[debug_type], debug_msg) + + +class CurlError(HTTPError): + def __init__(self, errno: int, message: str) -> None: + HTTPError.__init__(self, 599, message) + self.errno = errno + + +if __name__ == "__main__": + AsyncHTTPClient.configure(CurlAsyncHTTPClient) + main() diff --git a/venv/lib/python3.7/site-packages/tornado/escape.py b/venv/lib/python3.7/site-packages/tornado/escape.py new file mode 100644 index 0000000..b0ec332 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/escape.py @@ -0,0 +1,400 @@ +# +# Copyright 2009 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. + +"""Escaping/unescaping methods for HTML, JSON, URLs, and others. + +Also includes a few other miscellaneous string manipulation functions that +have crept in over time. +""" + +import html.entities +import json +import re +import urllib.parse + +from tornado.util import unicode_type + +import typing +from typing import Union, Any, Optional, Dict, List, Callable + + +_XHTML_ESCAPE_RE = re.compile("[&<>\"']") +_XHTML_ESCAPE_DICT = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", +} + + +def xhtml_escape(value: Union[str, bytes]) -> str: + """Escapes a string so it is valid within HTML or XML. + + Escapes the characters ``<``, ``>``, ``"``, ``'``, and ``&``. + When used in attribute values the escaped strings must be enclosed + in quotes. + + .. versionchanged:: 3.2 + + Added the single quote to the list of escaped characters. + """ + return _XHTML_ESCAPE_RE.sub( + lambda match: _XHTML_ESCAPE_DICT[match.group(0)], to_basestring(value) + ) + + +def xhtml_unescape(value: Union[str, bytes]) -> str: + """Un-escapes an XML-escaped string.""" + return re.sub(r"&(#?)(\w+?);", _convert_entity, _unicode(value)) + + +# The fact that json_encode wraps json.dumps is an implementation detail. +# Please see https://github.com/tornadoweb/tornado/pull/706 +# before sending a pull request that adds **kwargs to this function. +def json_encode(value: Any) -> str: + """JSON-encodes the given Python object.""" + # JSON permits but does not require forward slashes to be escaped. + # This is useful when json data is emitted in a <script> tag + # in HTML, as it prevents </script> tags from prematurely terminating + # the javascript. Some json libraries do this escaping by default, + # although python's standard library does not, so we do it here. + # http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped + return json.dumps(value).replace("</", "<\\/") + + +def json_decode(value: Union[str, bytes]) -> Any: + """Returns Python objects for the given JSON string. + + Supports both `str` and `bytes` inputs. + """ + return json.loads(to_basestring(value)) + + +def squeeze(value: str) -> str: + """Replace all sequences of whitespace chars with a single space.""" + return re.sub(r"[\x00-\x20]+", " ", value).strip() + + +def url_escape(value: Union[str, bytes], plus: bool = True) -> str: + """Returns a URL-encoded version of the given value. + + If ``plus`` is true (the default), spaces will be represented + as "+" instead of "%20". This is appropriate for query strings + but not for the path component of a URL. Note that this default + is the reverse of Python's urllib module. + + .. versionadded:: 3.1 + The ``plus`` argument + """ + quote = urllib.parse.quote_plus if plus else urllib.parse.quote + return quote(utf8(value)) + + +@typing.overload +def url_unescape(value: Union[str, bytes], encoding: None, plus: bool = True) -> bytes: + pass + + +@typing.overload # noqa: F811 +def url_unescape( + value: Union[str, bytes], encoding: str = "utf-8", plus: bool = True +) -> str: + pass + + +def url_unescape( # noqa: F811 + value: Union[str, bytes], encoding: Optional[str] = "utf-8", plus: bool = True +) -> Union[str, bytes]: + """Decodes the given value from a URL. + + The argument may be either a byte or unicode string. + + If encoding is None, the result will be a byte string. Otherwise, + the result is a unicode string in the specified encoding. + + If ``plus`` is true (the default), plus signs will be interpreted + as spaces (literal plus signs must be represented as "%2B"). This + is appropriate for query strings and form-encoded values but not + for the path component of a URL. Note that this default is the + reverse of Python's urllib module. + + .. versionadded:: 3.1 + The ``plus`` argument + """ + if encoding is None: + if plus: + # unquote_to_bytes doesn't have a _plus variant + value = to_basestring(value).replace("+", " ") + return urllib.parse.unquote_to_bytes(value) + else: + unquote = urllib.parse.unquote_plus if plus else urllib.parse.unquote + return unquote(to_basestring(value), encoding=encoding) + + +def parse_qs_bytes( + qs: str, keep_blank_values: bool = False, strict_parsing: bool = False +) -> Dict[str, List[bytes]]: + """Parses a query string like urlparse.parse_qs, but returns the + values as byte strings. + + Keys still become type str (interpreted as latin1 in python3!) + because it's too painful to keep them as byte strings in + python3 and in practice they're nearly always ascii anyway. + """ + # This is gross, but python3 doesn't give us another way. + # Latin1 is the universal donor of character encodings. + result = urllib.parse.parse_qs( + qs, keep_blank_values, strict_parsing, encoding="latin1", errors="strict" + ) + encoded = {} + for k, v in result.items(): + encoded[k] = [i.encode("latin1") for i in v] + return encoded + + +_UTF8_TYPES = (bytes, type(None)) + + +@typing.overload +def utf8(value: bytes) -> bytes: + pass + + +@typing.overload # noqa: F811 +def utf8(value: str) -> bytes: + pass + + +@typing.overload # noqa: F811 +def utf8(value: None) -> None: + pass + + +def utf8(value: Union[None, str, bytes]) -> Optional[bytes]: # noqa: F811 + """Converts a string argument to a byte string. + + If the argument is already a byte string or None, it is returned unchanged. + Otherwise it must be a unicode string and is encoded as utf8. + """ + if isinstance(value, _UTF8_TYPES): + return value + if not isinstance(value, unicode_type): + raise TypeError("Expected bytes, unicode, or None; got %r" % type(value)) + return value.encode("utf-8") + + +_TO_UNICODE_TYPES = (unicode_type, type(None)) + + +@typing.overload +def to_unicode(value: str) -> str: + pass + + +@typing.overload # noqa: F811 +def to_unicode(value: bytes) -> str: + pass + + +@typing.overload # noqa: F811 +def to_unicode(value: None) -> None: + pass + + +def to_unicode(value: Union[None, str, bytes]) -> Optional[str]: # noqa: F811 + """Converts a string argument to a unicode string. + + If the argument is already a unicode string or None, it is returned + unchanged. Otherwise it must be a byte string and is decoded as utf8. + """ + if isinstance(value, _TO_UNICODE_TYPES): + return value + if not isinstance(value, bytes): + raise TypeError("Expected bytes, unicode, or None; got %r" % type(value)) + return value.decode("utf-8") + + +# to_unicode was previously named _unicode not because it was private, +# but to avoid conflicts with the built-in unicode() function/type +_unicode = to_unicode + +# When dealing with the standard library across python 2 and 3 it is +# sometimes useful to have a direct conversion to the native string type +native_str = to_unicode +to_basestring = to_unicode + + +def recursive_unicode(obj: Any) -> Any: + """Walks a simple data structure, converting byte strings to unicode. + + Supports lists, tuples, and dictionaries. + """ + if isinstance(obj, dict): + return dict( + (recursive_unicode(k), recursive_unicode(v)) for (k, v) in obj.items() + ) + elif isinstance(obj, list): + return list(recursive_unicode(i) for i in obj) + elif isinstance(obj, tuple): + return tuple(recursive_unicode(i) for i in obj) + elif isinstance(obj, bytes): + return to_unicode(obj) + else: + return obj + + +# I originally used the regex from +# http://daringfireball.net/2010/07/improved_regex_for_matching_urls +# but it gets all exponential on certain patterns (such as too many trailing +# dots), causing the regex matcher to never return. +# This regex should avoid those problems. +# Use to_unicode instead of tornado.util.u - we don't want backslashes getting +# processed as escapes. +_URL_RE = re.compile( + to_unicode( + r"""\b((?:([\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\s&()]|&|")*(?:[^!"#$%&'()*+,.:;<=>?@\[\]^`{|}~\s]))|(?:\((?:[^\s&()]|&|")*\)))+)""" # noqa: E501 + ) +) + + +def linkify( + text: Union[str, bytes], + shorten: bool = False, + extra_params: Union[str, Callable[[str], str]] = "", + require_protocol: bool = False, + permitted_protocols: List[str] = ["http", "https"], +) -> str: + """Converts plain text into HTML with links. + + For example: ``linkify("Hello http://tornadoweb.org!")`` would return + ``Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!`` + + Parameters: + + * ``shorten``: Long urls will be shortened for display. + + * ``extra_params``: Extra text to include in the link tag, or a callable + taking the link as an argument and returning the extra text + e.g. ``linkify(text, extra_params='rel="nofollow" class="external"')``, + or:: + + def extra_params_cb(url): + if url.startswith("http://example.com"): + return 'class="internal"' + else: + return 'class="external" rel="nofollow"' + linkify(text, extra_params=extra_params_cb) + + * ``require_protocol``: Only linkify urls which include a protocol. If + this is False, urls such as www.facebook.com will also be linkified. + + * ``permitted_protocols``: List (or set) of protocols which should be + linkified, e.g. ``linkify(text, permitted_protocols=["http", "ftp", + "mailto"])``. It is very unsafe to include protocols such as + ``javascript``. + """ + if extra_params and not callable(extra_params): + extra_params = " " + extra_params.strip() + + def make_link(m: typing.Match) -> str: + url = m.group(1) + proto = m.group(2) + if require_protocol and not proto: + return url # not protocol, no linkify + + if proto and proto not in permitted_protocols: + return url # bad protocol, no linkify + + href = m.group(1) + if not proto: + href = "http://" + href # no proto specified, use http + + if callable(extra_params): + params = " " + extra_params(href).strip() + else: + params = extra_params + + # clip long urls. max_len is just an approximation + max_len = 30 + if shorten and len(url) > max_len: + before_clip = url + if proto: + proto_len = len(proto) + 1 + len(m.group(3) or "") # +1 for : + else: + proto_len = 0 + + parts = url[proto_len:].split("/") + if len(parts) > 1: + # Grab the whole host part plus the first bit of the path + # The path is usually not that interesting once shortened + # (no more slug, etc), so it really just provides a little + # extra indication of shortening. + url = ( + url[:proto_len] + + parts[0] + + "/" + + parts[1][:8].split("?")[0].split(".")[0] + ) + + if len(url) > max_len * 1.5: # still too long + url = url[:max_len] + + if url != before_clip: + amp = url.rfind("&") + # avoid splitting html char entities + if amp > max_len - 5: + url = url[:amp] + url += "..." + + if len(url) >= len(before_clip): + url = before_clip + else: + # full url is visible on mouse-over (for those who don't + # have a status bar, such as Safari by default) + params += ' title="%s"' % href + + return u'<a href="%s"%s>%s</a>' % (href, params, url) + + # First HTML-escape so that our strings are all safe. + # The regex is modified to avoid character entites other than & so + # that we won't pick up ", etc. + text = _unicode(xhtml_escape(text)) + return _URL_RE.sub(make_link, text) + + +def _convert_entity(m: typing.Match) -> str: + if m.group(1) == "#": + try: + if m.group(2)[:1].lower() == "x": + return chr(int(m.group(2)[1:], 16)) + else: + return chr(int(m.group(2))) + except ValueError: + return "&#%s;" % m.group(2) + try: + return _HTML_UNICODE_MAP[m.group(2)] + except KeyError: + return "&%s;" % m.group(2) + + +def _build_unicode_map() -> Dict[str, str]: + unicode_map = {} + for name, value in html.entities.name2codepoint.items(): + unicode_map[name] = chr(value) + return unicode_map + + +_HTML_UNICODE_MAP = _build_unicode_map() diff --git a/venv/lib/python3.7/site-packages/tornado/gen.py b/venv/lib/python3.7/site-packages/tornado/gen.py new file mode 100644 index 0000000..7cc7ec7 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/gen.py @@ -0,0 +1,845 @@ +"""``tornado.gen`` implements generator-based coroutines. + +.. note:: + + The "decorator and generator" approach in this module is a + precursor to native coroutines (using ``async def`` and ``await``) + which were introduced in Python 3.5. Applications that do not + require compatibility with older versions of Python should use + native coroutines instead. Some parts of this module are still + useful with native coroutines, notably `multi`, `sleep`, + `WaitIterator`, and `with_timeout`. Some of these functions have + counterparts in the `asyncio` module which may be used as well, + although the two may not necessarily be 100% compatible. + +Coroutines provide an easier way to work in an asynchronous +environment than chaining callbacks. Code using coroutines is +technically asynchronous, but it is written as a single generator +instead of a collection of separate functions. + +For example, here's a coroutine-based handler: + +.. testcode:: + + class GenAsyncHandler(RequestHandler): + @gen.coroutine + def get(self): + http_client = AsyncHTTPClient() + response = yield http_client.fetch("http://example.com") + do_something_with_response(response) + self.render("template.html") + +.. testoutput:: + :hide: + +Asynchronous functions in Tornado return an ``Awaitable`` or `.Future`; +yielding this object returns its result. + +You can also yield a list or dict of other yieldable objects, which +will be started at the same time and run in parallel; a list or dict +of results will be returned when they are all finished: + +.. testcode:: + + @gen.coroutine + def get(self): + http_client = AsyncHTTPClient() + response1, response2 = yield [http_client.fetch(url1), + http_client.fetch(url2)] + response_dict = yield dict(response3=http_client.fetch(url3), + response4=http_client.fetch(url4)) + response3 = response_dict['response3'] + response4 = response_dict['response4'] + +.. testoutput:: + :hide: + +If ``tornado.platform.twisted`` is imported, it is also possible to +yield Twisted's ``Deferred`` objects. See the `convert_yielded` +function to extend this mechanism. + +.. versionchanged:: 3.2 + Dict support added. + +.. versionchanged:: 4.1 + Support added for yielding ``asyncio`` Futures and Twisted Deferreds + via ``singledispatch``. + +""" +import asyncio +import builtins +import collections +from collections.abc import Generator +import concurrent.futures +import datetime +import functools +from functools import singledispatch +from inspect import isawaitable +import sys +import types + +from tornado.concurrent import ( + Future, + is_future, + chain_future, + future_set_exc_info, + future_add_done_callback, + future_set_result_unless_cancelled, +) +from tornado.ioloop import IOLoop +from tornado.log import app_log +from tornado.util import TimeoutError + +import typing +from typing import Union, Any, Callable, List, Type, Tuple, Awaitable, Dict + +if typing.TYPE_CHECKING: + from typing import Sequence, Deque, Optional, Set, Iterable # noqa: F401 + +_T = typing.TypeVar("_T") + +_Yieldable = Union[ + None, Awaitable, List[Awaitable], Dict[Any, Awaitable], concurrent.futures.Future +] + + +class KeyReuseError(Exception): + pass + + +class UnknownKeyError(Exception): + pass + + +class LeakedCallbackError(Exception): + pass + + +class BadYieldError(Exception): + pass + + +class ReturnValueIgnoredError(Exception): + pass + + +def _value_from_stopiteration(e: Union[StopIteration, "Return"]) -> Any: + try: + # StopIteration has a value attribute beginning in py33. + # So does our Return class. + return e.value + except AttributeError: + pass + try: + # Cython backports coroutine functionality by putting the value in + # e.args[0]. + return e.args[0] + except (AttributeError, IndexError): + return None + + +def _create_future() -> Future: + future = Future() # type: Future + # Fixup asyncio debug info by removing extraneous stack entries + source_traceback = getattr(future, "_source_traceback", ()) + while source_traceback: + # Each traceback entry is equivalent to a + # (filename, self.lineno, self.name, self.line) tuple + filename = source_traceback[-1][0] + if filename == __file__: + del source_traceback[-1] + else: + break + return future + + +def coroutine( + func: Callable[..., "Generator[Any, Any, _T]"] +) -> Callable[..., "Future[_T]"]: + """Decorator for asynchronous generators. + + For compatibility with older versions of Python, coroutines may + also "return" by raising the special exception `Return(value) + <Return>`. + + Functions with this decorator return a `.Future`. + + .. warning:: + + When exceptions occur inside a coroutine, the exception + information will be stored in the `.Future` object. You must + examine the result of the `.Future` object, or the exception + may go unnoticed by your code. This means yielding the function + if called from another coroutine, using something like + `.IOLoop.run_sync` for top-level calls, or passing the `.Future` + to `.IOLoop.add_future`. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + awaitable object instead. + + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + # type: (*Any, **Any) -> Future[_T] + # This function is type-annotated with a comment to work around + # https://bitbucket.org/pypy/pypy/issues/2868/segfault-with-args-type-annotation-in + future = _create_future() + try: + result = func(*args, **kwargs) + except (Return, StopIteration) as e: + result = _value_from_stopiteration(e) + except Exception: + future_set_exc_info(future, sys.exc_info()) + try: + return future + finally: + # Avoid circular references + future = None # type: ignore + else: + if isinstance(result, Generator): + # Inline the first iteration of Runner.run. This lets us + # avoid the cost of creating a Runner when the coroutine + # never actually yields, which in turn allows us to + # use "optional" coroutines in critical path code without + # performance penalty for the synchronous case. + try: + yielded = next(result) + except (StopIteration, Return) as e: + future_set_result_unless_cancelled( + future, _value_from_stopiteration(e) + ) + except Exception: + future_set_exc_info(future, sys.exc_info()) + else: + # Provide strong references to Runner objects as long + # as their result future objects also have strong + # references (typically from the parent coroutine's + # Runner). This keeps the coroutine's Runner alive. + # We do this by exploiting the public API + # add_done_callback() instead of putting a private + # attribute on the Future. + # (Github issues #1769, #2229). + runner = Runner(result, future, yielded) + future.add_done_callback(lambda _: runner) + yielded = None + try: + return future + finally: + # Subtle memory optimization: if next() raised an exception, + # the future's exc_info contains a traceback which + # includes this stack frame. This creates a cycle, + # which will be collected at the next full GC but has + # been shown to greatly increase memory usage of + # benchmarks (relative to the refcount-based scheme + # used in the absence of cycles). We can avoid the + # cycle by clearing the local variable after we return it. + future = None # type: ignore + future_set_result_unless_cancelled(future, result) + return future + + wrapper.__wrapped__ = func # type: ignore + wrapper.__tornado_coroutine__ = True # type: ignore + return wrapper + + +def is_coroutine_function(func: Any) -> bool: + """Return whether *func* is a coroutine function, i.e. a function + wrapped with `~.gen.coroutine`. + + .. versionadded:: 4.5 + """ + return getattr(func, "__tornado_coroutine__", False) + + +class Return(Exception): + """Special exception to return a value from a `coroutine`. + + If this exception is raised, its value argument is used as the + result of the coroutine:: + + @gen.coroutine + def fetch_json(url): + response = yield AsyncHTTPClient().fetch(url) + raise gen.Return(json_decode(response.body)) + + In Python 3.3, this exception is no longer necessary: the ``return`` + statement can be used directly to return a value (previously + ``yield`` and ``return`` with a value could not be combined in the + same function). + + By analogy with the return statement, the value argument is optional, + but it is never necessary to ``raise gen.Return()``. The ``return`` + statement can be used with no arguments instead. + """ + + def __init__(self, value: Any = None) -> None: + super(Return, self).__init__() + self.value = value + # Cython recognizes subclasses of StopIteration with a .args tuple. + self.args = (value,) + + +class WaitIterator(object): + """Provides an iterator to yield the results of awaitables as they finish. + + Yielding a set of awaitables like this: + + ``results = yield [awaitable1, awaitable2]`` + + pauses the coroutine until both ``awaitable1`` and ``awaitable2`` + return, and then restarts the coroutine with the results of both + awaitables. If either awaitable raises an exception, the + expression will raise that exception and all the results will be + lost. + + If you need to get the result of each awaitable as soon as possible, + or if you need the result of some awaitables even if others produce + errors, you can use ``WaitIterator``:: + + wait_iterator = gen.WaitIterator(awaitable1, awaitable2) + while not wait_iterator.done(): + try: + result = yield wait_iterator.next() + except Exception as e: + print("Error {} from {}".format(e, wait_iterator.current_future)) + else: + print("Result {} received from {} at {}".format( + result, wait_iterator.current_future, + wait_iterator.current_index)) + + Because results are returned as soon as they are available the + output from the iterator *will not be in the same order as the + input arguments*. If you need to know which future produced the + current result, you can use the attributes + ``WaitIterator.current_future``, or ``WaitIterator.current_index`` + to get the index of the awaitable from the input list. (if keyword + arguments were used in the construction of the `WaitIterator`, + ``current_index`` will use the corresponding keyword). + + On Python 3.5, `WaitIterator` implements the async iterator + protocol, so it can be used with the ``async for`` statement (note + that in this version the entire iteration is aborted if any value + raises an exception, while the previous example can continue past + individual errors):: + + async for result in gen.WaitIterator(future1, future2): + print("Result {} received from {} at {}".format( + result, wait_iterator.current_future, + wait_iterator.current_index)) + + .. versionadded:: 4.1 + + .. versionchanged:: 4.3 + Added ``async for`` support in Python 3.5. + + """ + + _unfinished = {} # type: Dict[Future, Union[int, str]] + + def __init__(self, *args: Future, **kwargs: Future) -> None: + if args and kwargs: + raise ValueError("You must provide args or kwargs, not both") + + if kwargs: + self._unfinished = dict((f, k) for (k, f) in kwargs.items()) + futures = list(kwargs.values()) # type: Sequence[Future] + else: + self._unfinished = dict((f, i) for (i, f) in enumerate(args)) + futures = args + + self._finished = collections.deque() # type: Deque[Future] + self.current_index = None # type: Optional[Union[str, int]] + self.current_future = None # type: Optional[Future] + self._running_future = None # type: Optional[Future] + + for future in futures: + future_add_done_callback(future, self._done_callback) + + def done(self) -> bool: + """Returns True if this iterator has no more results.""" + if self._finished or self._unfinished: + return False + # Clear the 'current' values when iteration is done. + self.current_index = self.current_future = None + return True + + def next(self) -> Future: + """Returns a `.Future` that will yield the next available result. + + Note that this `.Future` will not be the same object as any of + the inputs. + """ + self._running_future = Future() + + if self._finished: + self._return_result(self._finished.popleft()) + + return self._running_future + + def _done_callback(self, done: Future) -> None: + if self._running_future and not self._running_future.done(): + self._return_result(done) + else: + self._finished.append(done) + + def _return_result(self, done: Future) -> None: + """Called set the returned future's state that of the future + we yielded, and set the current future for the iterator. + """ + if self._running_future is None: + raise Exception("no future is running") + chain_future(done, self._running_future) + + self.current_future = done + self.current_index = self._unfinished.pop(done) + + def __aiter__(self) -> typing.AsyncIterator: + return self + + def __anext__(self) -> Future: + if self.done(): + # Lookup by name to silence pyflakes on older versions. + raise getattr(builtins, "StopAsyncIteration")() + return self.next() + + +def multi( + children: Union[List[_Yieldable], Dict[Any, _Yieldable]], + quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (), +) -> "Union[Future[List], Future[Dict]]": + """Runs multiple asynchronous operations in parallel. + + ``children`` may either be a list or a dict whose values are + yieldable objects. ``multi()`` returns a new yieldable + object that resolves to a parallel structure containing their + results. If ``children`` is a list, the result is a list of + results in the same order; if it is a dict, the result is a dict + with the same keys. + + That is, ``results = yield multi(list_of_futures)`` is equivalent + to:: + + results = [] + for future in list_of_futures: + results.append(yield future) + + If any children raise exceptions, ``multi()`` will raise the first + one. All others will be logged, unless they are of types + contained in the ``quiet_exceptions`` argument. + + In a ``yield``-based coroutine, it is not normally necessary to + call this function directly, since the coroutine runner will + do it automatically when a list or dict is yielded. However, + it is necessary in ``await``-based coroutines, or to pass + the ``quiet_exceptions`` argument. + + This function is available under the names ``multi()`` and ``Multi()`` + for historical reasons. + + Cancelling a `.Future` returned by ``multi()`` does not cancel its + children. `asyncio.gather` is similar to ``multi()``, but it does + cancel its children. + + .. versionchanged:: 4.2 + If multiple yieldables fail, any exceptions after the first + (which is raised) will be logged. Added the ``quiet_exceptions`` + argument to suppress this logging for selected exception types. + + .. versionchanged:: 4.3 + Replaced the class ``Multi`` and the function ``multi_future`` + with a unified function ``multi``. Added support for yieldables + other than ``YieldPoint`` and `.Future`. + + """ + return multi_future(children, quiet_exceptions=quiet_exceptions) + + +Multi = multi + + +def multi_future( + children: Union[List[_Yieldable], Dict[Any, _Yieldable]], + quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (), +) -> "Union[Future[List], Future[Dict]]": + """Wait for multiple asynchronous futures in parallel. + + Since Tornado 6.0, this function is exactly the same as `multi`. + + .. versionadded:: 4.0 + + .. versionchanged:: 4.2 + If multiple ``Futures`` fail, any exceptions after the first (which is + raised) will be logged. Added the ``quiet_exceptions`` + argument to suppress this logging for selected exception types. + + .. deprecated:: 4.3 + Use `multi` instead. + """ + if isinstance(children, dict): + keys = list(children.keys()) # type: Optional[List] + children_seq = children.values() # type: Iterable + else: + keys = None + children_seq = children + children_futs = list(map(convert_yielded, children_seq)) + assert all(is_future(i) or isinstance(i, _NullFuture) for i in children_futs) + unfinished_children = set(children_futs) + + future = _create_future() + if not children_futs: + future_set_result_unless_cancelled(future, {} if keys is not None else []) + + def callback(fut: Future) -> None: + unfinished_children.remove(fut) + if not unfinished_children: + result_list = [] + for f in children_futs: + try: + result_list.append(f.result()) + except Exception as e: + if future.done(): + if not isinstance(e, quiet_exceptions): + app_log.error( + "Multiple exceptions in yield list", exc_info=True + ) + else: + future_set_exc_info(future, sys.exc_info()) + if not future.done(): + if keys is not None: + future_set_result_unless_cancelled( + future, dict(zip(keys, result_list)) + ) + else: + future_set_result_unless_cancelled(future, result_list) + + listening = set() # type: Set[Future] + for f in children_futs: + if f not in listening: + listening.add(f) + future_add_done_callback(f, callback) + return future + + +def maybe_future(x: Any) -> Future: + """Converts ``x`` into a `.Future`. + + If ``x`` is already a `.Future`, it is simply returned; otherwise + it is wrapped in a new `.Future`. This is suitable for use as + ``result = yield gen.maybe_future(f())`` when you don't know whether + ``f()`` returns a `.Future` or not. + + .. deprecated:: 4.3 + This function only handles ``Futures``, not other yieldable objects. + Instead of `maybe_future`, check for the non-future result types + you expect (often just ``None``), and ``yield`` anything unknown. + """ + if is_future(x): + return x + else: + fut = _create_future() + fut.set_result(x) + return fut + + +def with_timeout( + timeout: Union[float, datetime.timedelta], + future: _Yieldable, + quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (), +) -> Future: + """Wraps a `.Future` (or other yieldable object) in a timeout. + + Raises `tornado.util.TimeoutError` if the input future does not + complete before ``timeout``, which may be specified in any form + allowed by `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or + an absolute time relative to `.IOLoop.time`) + + If the wrapped `.Future` fails after it has timed out, the exception + will be logged unless it is either of a type contained in + ``quiet_exceptions`` (which may be an exception type or a sequence of + types), or an ``asyncio.CancelledError``. + + The wrapped `.Future` is not canceled when the timeout expires, + permitting it to be reused. `asyncio.wait_for` is similar to this + function but it does cancel the wrapped `.Future` on timeout. + + .. versionadded:: 4.0 + + .. versionchanged:: 4.1 + Added the ``quiet_exceptions`` argument and the logging of unhandled + exceptions. + + .. versionchanged:: 4.4 + Added support for yieldable objects other than `.Future`. + + .. versionchanged:: 6.0.3 + ``asyncio.CancelledError`` is now always considered "quiet". + + """ + # It's tempting to optimize this by cancelling the input future on timeout + # instead of creating a new one, but A) we can't know if we are the only + # one waiting on the input future, so cancelling it might disrupt other + # callers and B) concurrent futures can only be cancelled while they are + # in the queue, so cancellation cannot reliably bound our waiting time. + future_converted = convert_yielded(future) + result = _create_future() + chain_future(future_converted, result) + io_loop = IOLoop.current() + + def error_callback(future: Future) -> None: + try: + future.result() + except asyncio.CancelledError: + pass + except Exception as e: + if not isinstance(e, quiet_exceptions): + app_log.error( + "Exception in Future %r after timeout", future, exc_info=True + ) + + def timeout_callback() -> None: + if not result.done(): + result.set_exception(TimeoutError("Timeout")) + # In case the wrapped future goes on to fail, log it. + future_add_done_callback(future_converted, error_callback) + + timeout_handle = io_loop.add_timeout(timeout, timeout_callback) + if isinstance(future_converted, Future): + # We know this future will resolve on the IOLoop, so we don't + # need the extra thread-safety of IOLoop.add_future (and we also + # don't care about StackContext here. + future_add_done_callback( + future_converted, lambda future: io_loop.remove_timeout(timeout_handle) + ) + else: + # concurrent.futures.Futures may resolve on any thread, so we + # need to route them back to the IOLoop. + io_loop.add_future( + future_converted, lambda future: io_loop.remove_timeout(timeout_handle) + ) + return result + + +def sleep(duration: float) -> "Future[None]": + """Return a `.Future` that resolves after the given number of seconds. + + When used with ``yield`` in a coroutine, this is a non-blocking + analogue to `time.sleep` (which should not be used in coroutines + because it is blocking):: + + yield gen.sleep(0.5) + + Note that calling this function on its own does nothing; you must + wait on the `.Future` it returns (usually by yielding it). + + .. versionadded:: 4.1 + """ + f = _create_future() + IOLoop.current().call_later( + duration, lambda: future_set_result_unless_cancelled(f, None) + ) + return f + + +class _NullFuture(object): + """_NullFuture resembles a Future that finished with a result of None. + + It's not actually a `Future` to avoid depending on a particular event loop. + Handled as a special case in the coroutine runner. + + We lie and tell the type checker that a _NullFuture is a Future so + we don't have to leak _NullFuture into lots of public APIs. But + this means that the type checker can't warn us when we're passing + a _NullFuture into a code path that doesn't understand what to do + with it. + """ + + def result(self) -> None: + return None + + def done(self) -> bool: + return True + + +# _null_future is used as a dummy value in the coroutine runner. It differs +# from moment in that moment always adds a delay of one IOLoop iteration +# while _null_future is processed as soon as possible. +_null_future = typing.cast(Future, _NullFuture()) + +moment = typing.cast(Future, _NullFuture()) +moment.__doc__ = """A special object which may be yielded to allow the IOLoop to run for +one iteration. + +This is not needed in normal use but it can be helpful in long-running +coroutines that are likely to yield Futures that are ready instantly. + +Usage: ``yield gen.moment`` + +In native coroutines, the equivalent of ``yield gen.moment`` is +``await asyncio.sleep(0)``. + +.. versionadded:: 4.0 + +.. deprecated:: 4.5 + ``yield None`` (or ``yield`` with no argument) is now equivalent to + ``yield gen.moment``. +""" + + +class Runner(object): + """Internal implementation of `tornado.gen.coroutine`. + + Maintains information about pending callbacks and their results. + + The results of the generator are stored in ``result_future`` (a + `.Future`) + """ + + def __init__( + self, + gen: "Generator[_Yieldable, Any, _T]", + result_future: "Future[_T]", + first_yielded: _Yieldable, + ) -> None: + self.gen = gen + self.result_future = result_future + self.future = _null_future # type: Union[None, Future] + self.running = False + self.finished = False + self.io_loop = IOLoop.current() + if self.handle_yield(first_yielded): + gen = result_future = first_yielded = None # type: ignore + self.run() + + def run(self) -> None: + """Starts or resumes the generator, running until it reaches a + yield point that is not ready. + """ + if self.running or self.finished: + return + try: + self.running = True + while True: + future = self.future + if future is None: + raise Exception("No pending future") + if not future.done(): + return + self.future = None + try: + exc_info = None + + try: + value = future.result() + except Exception: + exc_info = sys.exc_info() + future = None + + if exc_info is not None: + try: + yielded = self.gen.throw(*exc_info) # type: ignore + finally: + # Break up a reference to itself + # for faster GC on CPython. + exc_info = None + else: + yielded = self.gen.send(value) + + except (StopIteration, Return) as e: + self.finished = True + self.future = _null_future + future_set_result_unless_cancelled( + self.result_future, _value_from_stopiteration(e) + ) + self.result_future = None # type: ignore + return + except Exception: + self.finished = True + self.future = _null_future + future_set_exc_info(self.result_future, sys.exc_info()) + self.result_future = None # type: ignore + return + if not self.handle_yield(yielded): + return + yielded = None + finally: + self.running = False + + def handle_yield(self, yielded: _Yieldable) -> bool: + try: + self.future = convert_yielded(yielded) + except BadYieldError: + self.future = Future() + future_set_exc_info(self.future, sys.exc_info()) + + if self.future is moment: + self.io_loop.add_callback(self.run) + return False + elif self.future is None: + raise Exception("no pending future") + elif not self.future.done(): + + def inner(f: Any) -> None: + # Break a reference cycle to speed GC. + f = None # noqa: F841 + self.run() + + self.io_loop.add_future(self.future, inner) + return False + return True + + def handle_exception( + self, typ: Type[Exception], value: Exception, tb: types.TracebackType + ) -> bool: + if not self.running and not self.finished: + self.future = Future() + future_set_exc_info(self.future, (typ, value, tb)) + self.run() + return True + else: + return False + + +# Convert Awaitables into Futures. +try: + _wrap_awaitable = asyncio.ensure_future +except AttributeError: + # asyncio.ensure_future was introduced in Python 3.4.4, but + # Debian jessie still ships with 3.4.2 so try the old name. + _wrap_awaitable = getattr(asyncio, "async") + + +def convert_yielded(yielded: _Yieldable) -> Future: + """Convert a yielded object into a `.Future`. + + The default implementation accepts lists, dictionaries, and + Futures. This has the side effect of starting any coroutines that + did not start themselves, similar to `asyncio.ensure_future`. + + If the `~functools.singledispatch` library is available, this function + may be extended to support additional types. For example:: + + @convert_yielded.register(asyncio.Future) + def _(asyncio_future): + return tornado.platform.asyncio.to_tornado_future(asyncio_future) + + .. versionadded:: 4.1 + + """ + if yielded is None or yielded is moment: + return moment + elif yielded is _null_future: + return _null_future + elif isinstance(yielded, (list, dict)): + return multi(yielded) # type: ignore + elif is_future(yielded): + return typing.cast(Future, yielded) + elif isawaitable(yielded): + return _wrap_awaitable(yielded) # type: ignore + else: + raise BadYieldError("yielded unknown object %r" % (yielded,)) + + +convert_yielded = singledispatch(convert_yielded) diff --git a/venv/lib/python3.7/site-packages/tornado/http1connection.py b/venv/lib/python3.7/site-packages/tornado/http1connection.py new file mode 100644 index 0000000..434f16a --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/http1connection.py @@ -0,0 +1,836 @@ +# +# Copyright 2014 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. + +"""Client and server implementations of HTTP/1.x. + +.. versionadded:: 4.0 +""" + +import asyncio +import logging +import re +import types + +from tornado.concurrent import ( + Future, + future_add_done_callback, + future_set_result_unless_cancelled, +) +from tornado.escape import native_str, utf8 +from tornado import gen +from tornado import httputil +from tornado import iostream +from tornado.log import gen_log, app_log +from tornado.util import GzipDecompressor + + +from typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple + + +class _QuietException(Exception): + def __init__(self) -> None: + pass + + +class _ExceptionLoggingContext(object): + """Used with the ``with`` statement when calling delegate methods to + log any exceptions with the given logger. Any exceptions caught are + converted to _QuietException + """ + + def __init__(self, logger: logging.Logger) -> None: + self.logger = logger + + def __enter__(self) -> None: + pass + + def __exit__( + self, + typ: "Optional[Type[BaseException]]", + value: Optional[BaseException], + tb: types.TracebackType, + ) -> None: + if value is not None: + assert typ is not None + self.logger.error("Uncaught exception", exc_info=(typ, value, tb)) + raise _QuietException + + +class HTTP1ConnectionParameters(object): + """Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`. + """ + + def __init__( + self, + no_keep_alive: bool = False, + chunk_size: int = None, + max_header_size: int = None, + header_timeout: float = None, + max_body_size: int = None, + body_timeout: float = None, + decompress: bool = False, + ) -> None: + """ + :arg bool no_keep_alive: If true, always close the connection after + one request. + :arg int chunk_size: how much data to read into memory at once + :arg int max_header_size: maximum amount of data for HTTP headers + :arg float header_timeout: how long to wait for all headers (seconds) + :arg int max_body_size: maximum amount of data for body + :arg float body_timeout: how long to wait while reading body (seconds) + :arg bool decompress: if true, decode incoming + ``Content-Encoding: gzip`` + """ + self.no_keep_alive = no_keep_alive + self.chunk_size = chunk_size or 65536 + self.max_header_size = max_header_size or 65536 + self.header_timeout = header_timeout + self.max_body_size = max_body_size + self.body_timeout = body_timeout + self.decompress = decompress + + +class HTTP1Connection(httputil.HTTPConnection): + """Implements the HTTP/1.x protocol. + + This class can be on its own for clients, or via `HTTP1ServerConnection` + for servers. + """ + + def __init__( + self, + stream: iostream.IOStream, + is_client: bool, + params: HTTP1ConnectionParameters = None, + context: object = None, + ) -> None: + """ + :arg stream: an `.IOStream` + :arg bool is_client: client or server + :arg params: a `.HTTP1ConnectionParameters` instance or ``None`` + :arg context: an opaque application-defined object that can be accessed + as ``connection.context``. + """ + self.is_client = is_client + self.stream = stream + if params is None: + params = HTTP1ConnectionParameters() + self.params = params + self.context = context + self.no_keep_alive = params.no_keep_alive + # The body limits can be altered by the delegate, so save them + # here instead of just referencing self.params later. + self._max_body_size = self.params.max_body_size or self.stream.max_buffer_size + self._body_timeout = self.params.body_timeout + # _write_finished is set to True when finish() has been called, + # i.e. there will be no more data sent. Data may still be in the + # stream's write buffer. + self._write_finished = False + # True when we have read the entire incoming body. + self._read_finished = False + # _finish_future resolves when all data has been written and flushed + # to the IOStream. + self._finish_future = Future() # type: Future[None] + # If true, the connection should be closed after this request + # (after the response has been written in the server side, + # and after it has been read in the client) + self._disconnect_on_finish = False + self._clear_callbacks() + # Save the start lines after we read or write them; they + # affect later processing (e.g. 304 responses and HEAD methods + # have content-length but no bodies) + self._request_start_line = None # type: Optional[httputil.RequestStartLine] + self._response_start_line = None # type: Optional[httputil.ResponseStartLine] + self._request_headers = None # type: Optional[httputil.HTTPHeaders] + # True if we are writing output with chunked encoding. + self._chunking_output = False + # While reading a body with a content-length, this is the + # amount left to read. + self._expected_content_remaining = None # type: Optional[int] + # A Future for our outgoing writes, returned by IOStream.write. + self._pending_write = None # type: Optional[Future[None]] + + def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]: + """Read a single HTTP response. + + Typical client-mode usage is to write a request using `write_headers`, + `write`, and `finish`, and then call ``read_response``. + + :arg delegate: a `.HTTPMessageDelegate` + + Returns a `.Future` that resolves to a bool after the full response has + been read. The result is true if the stream is still open. + """ + if self.params.decompress: + delegate = _GzipMessageDelegate(delegate, self.params.chunk_size) + return self._read_message(delegate) + + async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool: + need_delegate_close = False + try: + header_future = self.stream.read_until_regex( + b"\r?\n\r?\n", max_bytes=self.params.max_header_size + ) + if self.params.header_timeout is None: + header_data = await header_future + else: + try: + header_data = await gen.with_timeout( + self.stream.io_loop.time() + self.params.header_timeout, + header_future, + quiet_exceptions=iostream.StreamClosedError, + ) + except gen.TimeoutError: + self.close() + return False + start_line_str, headers = self._parse_headers(header_data) + if self.is_client: + resp_start_line = httputil.parse_response_start_line(start_line_str) + self._response_start_line = resp_start_line + start_line = ( + resp_start_line + ) # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine] + # TODO: this will need to change to support client-side keepalive + self._disconnect_on_finish = False + else: + req_start_line = httputil.parse_request_start_line(start_line_str) + self._request_start_line = req_start_line + self._request_headers = headers + start_line = req_start_line + self._disconnect_on_finish = not self._can_keep_alive( + req_start_line, headers + ) + need_delegate_close = True + with _ExceptionLoggingContext(app_log): + header_recv_future = delegate.headers_received(start_line, headers) + if header_recv_future is not None: + await header_recv_future + if self.stream is None: + # We've been detached. + need_delegate_close = False + return False + skip_body = False + if self.is_client: + assert isinstance(start_line, httputil.ResponseStartLine) + if ( + self._request_start_line is not None + and self._request_start_line.method == "HEAD" + ): + skip_body = True + code = start_line.code + if code == 304: + # 304 responses may include the content-length header + # but do not actually have a body. + # http://tools.ietf.org/html/rfc7230#section-3.3 + skip_body = True + if code >= 100 and code < 200: + # 1xx responses should never indicate the presence of + # a body. + if "Content-Length" in headers or "Transfer-Encoding" in headers: + raise httputil.HTTPInputError( + "Response code %d cannot have body" % code + ) + # TODO: client delegates will get headers_received twice + # in the case of a 100-continue. Document or change? + await self._read_message(delegate) + else: + if headers.get("Expect") == "100-continue" and not self._write_finished: + self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n") + if not skip_body: + body_future = self._read_body( + resp_start_line.code if self.is_client else 0, headers, delegate + ) + if body_future is not None: + if self._body_timeout is None: + await body_future + else: + try: + await gen.with_timeout( + self.stream.io_loop.time() + self._body_timeout, + body_future, + quiet_exceptions=iostream.StreamClosedError, + ) + except gen.TimeoutError: + gen_log.info("Timeout reading body from %s", self.context) + self.stream.close() + return False + self._read_finished = True + if not self._write_finished or self.is_client: + need_delegate_close = False + with _ExceptionLoggingContext(app_log): + delegate.finish() + # If we're waiting for the application to produce an asynchronous + # response, and we're not detached, register a close callback + # on the stream (we didn't need one while we were reading) + if ( + not self._finish_future.done() + and self.stream is not None + and not self.stream.closed() + ): + self.stream.set_close_callback(self._on_connection_close) + await self._finish_future + if self.is_client and self._disconnect_on_finish: + self.close() + if self.stream is None: + return False + except httputil.HTTPInputError as e: + gen_log.info("Malformed HTTP message from %s: %s", self.context, e) + if not self.is_client: + await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n") + self.close() + return False + finally: + if need_delegate_close: + with _ExceptionLoggingContext(app_log): + delegate.on_connection_close() + header_future = None # type: ignore + self._clear_callbacks() + return True + + def _clear_callbacks(self) -> None: + """Clears the callback attributes. + + This allows the request handler to be garbage collected more + quickly in CPython by breaking up reference cycles. + """ + self._write_callback = None + self._write_future = None # type: Optional[Future[None]] + self._close_callback = None # type: Optional[Callable[[], None]] + if self.stream is not None: + self.stream.set_close_callback(None) + + def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None: + """Sets a callback that will be run when the connection is closed. + + Note that this callback is slightly different from + `.HTTPMessageDelegate.on_connection_close`: The + `.HTTPMessageDelegate` method is called when the connection is + closed while recieving a message. This callback is used when + there is not an active delegate (for example, on the server + side this callback is used if the client closes the connection + after sending its request but before receiving all the + response. + """ + self._close_callback = callback + + def _on_connection_close(self) -> None: + # Note that this callback is only registered on the IOStream + # when we have finished reading the request and are waiting for + # the application to produce its response. + if self._close_callback is not None: + callback = self._close_callback + self._close_callback = None + callback() + if not self._finish_future.done(): + future_set_result_unless_cancelled(self._finish_future, None) + self._clear_callbacks() + + def close(self) -> None: + if self.stream is not None: + self.stream.close() + self._clear_callbacks() + if not self._finish_future.done(): + future_set_result_unless_cancelled(self._finish_future, None) + + def detach(self) -> iostream.IOStream: + """Take control of the underlying stream. + + Returns the underlying `.IOStream` object and stops all further + HTTP processing. May only be called during + `.HTTPMessageDelegate.headers_received`. Intended for implementing + protocols like websockets that tunnel over an HTTP handshake. + """ + self._clear_callbacks() + stream = self.stream + self.stream = None # type: ignore + if not self._finish_future.done(): + future_set_result_unless_cancelled(self._finish_future, None) + return stream + + def set_body_timeout(self, timeout: float) -> None: + """Sets the body timeout for a single request. + + Overrides the value from `.HTTP1ConnectionParameters`. + """ + self._body_timeout = timeout + + def set_max_body_size(self, max_body_size: int) -> None: + """Sets the body size limit for a single request. + + Overrides the value from `.HTTP1ConnectionParameters`. + """ + self._max_body_size = max_body_size + + def write_headers( + self, + start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], + headers: httputil.HTTPHeaders, + chunk: bytes = None, + ) -> "Future[None]": + """Implements `.HTTPConnection.write_headers`.""" + lines = [] + if self.is_client: + assert isinstance(start_line, httputil.RequestStartLine) + self._request_start_line = start_line + lines.append(utf8("%s %s HTTP/1.1" % (start_line[0], start_line[1]))) + # Client requests with a non-empty body must have either a + # Content-Length or a Transfer-Encoding. + self._chunking_output = ( + start_line.method in ("POST", "PUT", "PATCH") + and "Content-Length" not in headers + and ( + "Transfer-Encoding" not in headers + or headers["Transfer-Encoding"] == "chunked" + ) + ) + else: + assert isinstance(start_line, httputil.ResponseStartLine) + assert self._request_start_line is not None + assert self._request_headers is not None + self._response_start_line = start_line + lines.append(utf8("HTTP/1.1 %d %s" % (start_line[1], start_line[2]))) + self._chunking_output = ( + # TODO: should this use + # self._request_start_line.version or + # start_line.version? + self._request_start_line.version == "HTTP/1.1" + # 1xx, 204 and 304 responses have no body (not even a zero-length + # body), and so should not have either Content-Length or + # Transfer-Encoding headers. + and start_line.code not in (204, 304) + and (start_line.code < 100 or start_line.code >= 200) + # No need to chunk the output if a Content-Length is specified. + and "Content-Length" not in headers + # Applications are discouraged from touching Transfer-Encoding, + # but if they do, leave it alone. + and "Transfer-Encoding" not in headers + ) + # If connection to a 1.1 client will be closed, inform client + if ( + self._request_start_line.version == "HTTP/1.1" + and self._disconnect_on_finish + ): + headers["Connection"] = "close" + # If a 1.0 client asked for keep-alive, add the header. + if ( + self._request_start_line.version == "HTTP/1.0" + and self._request_headers.get("Connection", "").lower() == "keep-alive" + ): + headers["Connection"] = "Keep-Alive" + if self._chunking_output: + headers["Transfer-Encoding"] = "chunked" + if not self.is_client and ( + self._request_start_line.method == "HEAD" + or cast(httputil.ResponseStartLine, start_line).code == 304 + ): + self._expected_content_remaining = 0 + elif "Content-Length" in headers: + self._expected_content_remaining = int(headers["Content-Length"]) + else: + self._expected_content_remaining = None + # TODO: headers are supposed to be of type str, but we still have some + # cases that let bytes slip through. Remove these native_str calls when those + # are fixed. + header_lines = ( + native_str(n) + ": " + native_str(v) for n, v in headers.get_all() + ) + lines.extend(l.encode("latin1") for l in header_lines) + for line in lines: + if b"\n" in line: + raise ValueError("Newline in header: " + repr(line)) + future = None + if self.stream.closed(): + future = self._write_future = Future() + future.set_exception(iostream.StreamClosedError()) + future.exception() + else: + future = self._write_future = Future() + data = b"\r\n".join(lines) + b"\r\n\r\n" + if chunk: + data += self._format_chunk(chunk) + self._pending_write = self.stream.write(data) + future_add_done_callback(self._pending_write, self._on_write_complete) + return future + + def _format_chunk(self, chunk: bytes) -> bytes: + if self._expected_content_remaining is not None: + self._expected_content_remaining -= len(chunk) + if self._expected_content_remaining < 0: + # Close the stream now to stop further framing errors. + self.stream.close() + raise httputil.HTTPOutputError( + "Tried to write more data than Content-Length" + ) + if self._chunking_output and chunk: + # Don't write out empty chunks because that means END-OF-STREAM + # with chunked encoding + return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n" + else: + return chunk + + def write(self, chunk: bytes) -> "Future[None]": + """Implements `.HTTPConnection.write`. + + For backwards compatibility it is allowed but deprecated to + skip `write_headers` and instead call `write()` with a + pre-encoded header block. + """ + future = None + if self.stream.closed(): + future = self._write_future = Future() + self._write_future.set_exception(iostream.StreamClosedError()) + self._write_future.exception() + else: + future = self._write_future = Future() + self._pending_write = self.stream.write(self._format_chunk(chunk)) + future_add_done_callback(self._pending_write, self._on_write_complete) + return future + + def finish(self) -> None: + """Implements `.HTTPConnection.finish`.""" + if ( + self._expected_content_remaining is not None + and self._expected_content_remaining != 0 + and not self.stream.closed() + ): + self.stream.close() + raise httputil.HTTPOutputError( + "Tried to write %d bytes less than Content-Length" + % self._expected_content_remaining + ) + if self._chunking_output: + if not self.stream.closed(): + self._pending_write = self.stream.write(b"0\r\n\r\n") + self._pending_write.add_done_callback(self._on_write_complete) + self._write_finished = True + # If the app finished the request while we're still reading, + # divert any remaining data away from the delegate and + # close the connection when we're done sending our response. + # Closing the connection is the only way to avoid reading the + # whole input body. + if not self._read_finished: + self._disconnect_on_finish = True + # No more data is coming, so instruct TCP to send any remaining + # data immediately instead of waiting for a full packet or ack. + self.stream.set_nodelay(True) + if self._pending_write is None: + self._finish_request(None) + else: + future_add_done_callback(self._pending_write, self._finish_request) + + def _on_write_complete(self, future: "Future[None]") -> None: + exc = future.exception() + if exc is not None and not isinstance(exc, iostream.StreamClosedError): + future.result() + if self._write_callback is not None: + callback = self._write_callback + self._write_callback = None + self.stream.io_loop.add_callback(callback) + if self._write_future is not None: + future = self._write_future + self._write_future = None + future_set_result_unless_cancelled(future, None) + + def _can_keep_alive( + self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders + ) -> bool: + if self.params.no_keep_alive: + return False + connection_header = headers.get("Connection") + if connection_header is not None: + connection_header = connection_header.lower() + if start_line.version == "HTTP/1.1": + return connection_header != "close" + elif ( + "Content-Length" in headers + or headers.get("Transfer-Encoding", "").lower() == "chunked" + or getattr(start_line, "method", None) in ("HEAD", "GET") + ): + # start_line may be a request or response start line; only + # the former has a method attribute. + return connection_header == "keep-alive" + return False + + def _finish_request(self, future: "Optional[Future[None]]") -> None: + self._clear_callbacks() + if not self.is_client and self._disconnect_on_finish: + self.close() + return + # Turn Nagle's algorithm back on, leaving the stream in its + # default state for the next request. + self.stream.set_nodelay(False) + if not self._finish_future.done(): + future_set_result_unless_cancelled(self._finish_future, None) + + def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]: + # The lstrip removes newlines that some implementations sometimes + # insert between messages of a reused connection. Per RFC 7230, + # we SHOULD ignore at least one empty line before the request. + # http://tools.ietf.org/html/rfc7230#section-3.5 + data_str = native_str(data.decode("latin1")).lstrip("\r\n") + # RFC 7230 section allows for both CRLF and bare LF. + eol = data_str.find("\n") + start_line = data_str[:eol].rstrip("\r") + headers = httputil.HTTPHeaders.parse(data_str[eol:]) + return start_line, headers + + def _read_body( + self, + code: int, + headers: httputil.HTTPHeaders, + delegate: httputil.HTTPMessageDelegate, + ) -> Optional[Awaitable[None]]: + if "Content-Length" in headers: + if "Transfer-Encoding" in headers: + # Response cannot contain both Content-Length and + # Transfer-Encoding headers. + # http://tools.ietf.org/html/rfc7230#section-3.3.3 + raise httputil.HTTPInputError( + "Response with both Transfer-Encoding and Content-Length" + ) + if "," in headers["Content-Length"]: + # Proxies sometimes cause Content-Length headers to get + # duplicated. If all the values are identical then we can + # use them but if they differ it's an error. + pieces = re.split(r",\s*", headers["Content-Length"]) + if any(i != pieces[0] for i in pieces): + raise httputil.HTTPInputError( + "Multiple unequal Content-Lengths: %r" + % headers["Content-Length"] + ) + headers["Content-Length"] = pieces[0] + + try: + content_length = int(headers["Content-Length"]) # type: Optional[int] + except ValueError: + # Handles non-integer Content-Length value. + raise httputil.HTTPInputError( + "Only integer Content-Length is allowed: %s" + % headers["Content-Length"] + ) + + if cast(int, content_length) > self._max_body_size: + raise httputil.HTTPInputError("Content-Length too long") + else: + content_length = None + + if code == 204: + # This response code is not allowed to have a non-empty body, + # and has an implicit length of zero instead of read-until-close. + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 + if "Transfer-Encoding" in headers or content_length not in (None, 0): + raise httputil.HTTPInputError( + "Response with code %d should not have body" % code + ) + content_length = 0 + + if content_length is not None: + return self._read_fixed_body(content_length, delegate) + if headers.get("Transfer-Encoding", "").lower() == "chunked": + return self._read_chunked_body(delegate) + if self.is_client: + return self._read_body_until_close(delegate) + return None + + async def _read_fixed_body( + self, content_length: int, delegate: httputil.HTTPMessageDelegate + ) -> None: + while content_length > 0: + body = await self.stream.read_bytes( + min(self.params.chunk_size, content_length), partial=True + ) + content_length -= len(body) + if not self._write_finished or self.is_client: + with _ExceptionLoggingContext(app_log): + ret = delegate.data_received(body) + if ret is not None: + await ret + + async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None: + # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1 + total_size = 0 + while True: + chunk_len_str = await self.stream.read_until(b"\r\n", max_bytes=64) + chunk_len = int(chunk_len_str.strip(), 16) + if chunk_len == 0: + crlf = await self.stream.read_bytes(2) + if crlf != b"\r\n": + raise httputil.HTTPInputError( + "improperly terminated chunked request" + ) + return + total_size += chunk_len + if total_size > self._max_body_size: + raise httputil.HTTPInputError("chunked body too large") + bytes_to_read = chunk_len + while bytes_to_read: + chunk = await self.stream.read_bytes( + min(bytes_to_read, self.params.chunk_size), partial=True + ) + bytes_to_read -= len(chunk) + if not self._write_finished or self.is_client: + with _ExceptionLoggingContext(app_log): + ret = delegate.data_received(chunk) + if ret is not None: + await ret + # chunk ends with \r\n + crlf = await self.stream.read_bytes(2) + assert crlf == b"\r\n" + + async def _read_body_until_close( + self, delegate: httputil.HTTPMessageDelegate + ) -> None: + body = await self.stream.read_until_close() + if not self._write_finished or self.is_client: + with _ExceptionLoggingContext(app_log): + ret = delegate.data_received(body) + if ret is not None: + await ret + + +class _GzipMessageDelegate(httputil.HTTPMessageDelegate): + """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``. + """ + + def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None: + self._delegate = delegate + self._chunk_size = chunk_size + self._decompressor = None # type: Optional[GzipDecompressor] + + def headers_received( + self, + start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], + headers: httputil.HTTPHeaders, + ) -> Optional[Awaitable[None]]: + if headers.get("Content-Encoding") == "gzip": + self._decompressor = GzipDecompressor() + # Downstream delegates will only see uncompressed data, + # so rename the content-encoding header. + # (but note that curl_httpclient doesn't do this). + headers.add("X-Consumed-Content-Encoding", headers["Content-Encoding"]) + del headers["Content-Encoding"] + return self._delegate.headers_received(start_line, headers) + + async def data_received(self, chunk: bytes) -> None: + if self._decompressor: + compressed_data = chunk + while compressed_data: + decompressed = self._decompressor.decompress( + compressed_data, self._chunk_size + ) + if decompressed: + ret = self._delegate.data_received(decompressed) + if ret is not None: + await ret + compressed_data = self._decompressor.unconsumed_tail + else: + ret = self._delegate.data_received(chunk) + if ret is not None: + await ret + + def finish(self) -> None: + if self._decompressor is not None: + tail = self._decompressor.flush() + if tail: + # The tail should always be empty: decompress returned + # all that it can in data_received and the only + # purpose of the flush call is to detect errors such + # as truncated input. If we did legitimately get a new + # chunk at this point we'd need to change the + # interface to make finish() a coroutine. + raise ValueError( + "decompressor.flush returned data; possile truncated input" + ) + return self._delegate.finish() + + def on_connection_close(self) -> None: + return self._delegate.on_connection_close() + + +class HTTP1ServerConnection(object): + """An HTTP/1.x server.""" + + def __init__( + self, + stream: iostream.IOStream, + params: HTTP1ConnectionParameters = None, + context: object = None, + ) -> None: + """ + :arg stream: an `.IOStream` + :arg params: a `.HTTP1ConnectionParameters` or None + :arg context: an opaque application-defined object that is accessible + as ``connection.context`` + """ + self.stream = stream + if params is None: + params = HTTP1ConnectionParameters() + self.params = params + self.context = context + self._serving_future = None # type: Optional[Future[None]] + + async def close(self) -> None: + """Closes the connection. + + Returns a `.Future` that resolves after the serving loop has exited. + """ + self.stream.close() + # Block until the serving loop is done, but ignore any exceptions + # (start_serving is already responsible for logging them). + assert self._serving_future is not None + try: + await self._serving_future + except Exception: + pass + + def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None: + """Starts serving requests on this connection. + + :arg delegate: a `.HTTPServerConnectionDelegate` + """ + assert isinstance(delegate, httputil.HTTPServerConnectionDelegate) + fut = gen.convert_yielded(self._server_request_loop(delegate)) + self._serving_future = fut + # Register the future on the IOLoop so its errors get logged. + self.stream.io_loop.add_future(fut, lambda f: f.result()) + + async def _server_request_loop( + self, delegate: httputil.HTTPServerConnectionDelegate + ) -> None: + try: + while True: + conn = HTTP1Connection(self.stream, False, self.params, self.context) + request_delegate = delegate.start_request(self, conn) + try: + ret = await conn.read_response(request_delegate) + except ( + iostream.StreamClosedError, + iostream.UnsatisfiableReadError, + asyncio.CancelledError, + ): + return + except _QuietException: + # This exception was already logged. + conn.close() + return + except Exception: + gen_log.error("Uncaught exception", exc_info=True) + conn.close() + return + if not ret: + return + await asyncio.sleep(0) + finally: + delegate.on_close(self) diff --git a/venv/lib/python3.7/site-packages/tornado/httpclient.py b/venv/lib/python3.7/site-packages/tornado/httpclient.py new file mode 100644 index 0000000..882600a --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/httpclient.py @@ -0,0 +1,781 @@ +"""Blocking and non-blocking HTTP client interfaces. + +This module defines a common interface shared by two implementations, +``simple_httpclient`` and ``curl_httpclient``. Applications may either +instantiate their chosen implementation class directly or use the +`AsyncHTTPClient` class from this module, which selects an implementation +that can be overridden with the `AsyncHTTPClient.configure` method. + +The default implementation is ``simple_httpclient``, and this is expected +to be suitable for most users' needs. However, some applications may wish +to switch to ``curl_httpclient`` for reasons such as the following: + +* ``curl_httpclient`` has some features not found in ``simple_httpclient``, + including support for HTTP proxies and the ability to use a specified + network interface. + +* ``curl_httpclient`` is more likely to be compatible with sites that are + not-quite-compliant with the HTTP spec, or sites that use little-exercised + features of HTTP. + +* ``curl_httpclient`` is faster. + +Note that if you are using ``curl_httpclient``, it is highly +recommended that you use a recent version of ``libcurl`` and +``pycurl``. Currently the minimum supported version of libcurl is +7.22.0, and the minimum version of pycurl is 7.18.2. It is highly +recommended that your ``libcurl`` installation is built with +asynchronous DNS resolver (threaded or c-ares), otherwise you may +encounter various problems with request timeouts (for more +information, see +http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUTMS +and comments in curl_httpclient.py). + +To select ``curl_httpclient``, call `AsyncHTTPClient.configure` at startup:: + + AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient") +""" + +import datetime +import functools +from io import BytesIO +import ssl +import time +import weakref + +from tornado.concurrent import ( + Future, + future_set_result_unless_cancelled, + future_set_exception_unless_cancelled, +) +from tornado.escape import utf8, native_str +from tornado import gen, httputil +from tornado.ioloop import IOLoop +from tornado.util import Configurable + +from typing import Type, Any, Union, Dict, Callable, Optional, cast, Awaitable + + +class HTTPClient(object): + """A blocking HTTP client. + + This interface is provided to make it easier to share code between + synchronous and asynchronous applications. Applications that are + running an `.IOLoop` must use `AsyncHTTPClient` instead. + + Typical usage looks like this:: + + http_client = httpclient.HTTPClient() + try: + response = http_client.fetch("http://www.google.com/") + print(response.body) + except httpclient.HTTPError as e: + # HTTPError is raised for non-200 responses; the response + # can be found in e.response. + print("Error: " + str(e)) + except Exception as e: + # Other errors are possible, such as IOError. + print("Error: " + str(e)) + http_client.close() + + .. versionchanged:: 5.0 + + Due to limitations in `asyncio`, it is no longer possible to + use the synchronous ``HTTPClient`` while an `.IOLoop` is running. + Use `AsyncHTTPClient` instead. + + """ + + def __init__( + self, async_client_class: Type["AsyncHTTPClient"] = None, **kwargs: Any + ) -> None: + # Initialize self._closed at the beginning of the constructor + # so that an exception raised here doesn't lead to confusing + # failures in __del__. + self._closed = True + self._io_loop = IOLoop(make_current=False) + if async_client_class is None: + async_client_class = AsyncHTTPClient + + # Create the client while our IOLoop is "current", without + # clobbering the thread's real current IOLoop (if any). + async def make_client() -> "AsyncHTTPClient": + await gen.sleep(0) + assert async_client_class is not None + return async_client_class(**kwargs) + + self._async_client = self._io_loop.run_sync(make_client) + self._closed = False + + def __del__(self) -> None: + self.close() + + def close(self) -> None: + """Closes the HTTPClient, freeing any resources used.""" + if not self._closed: + self._async_client.close() + self._io_loop.close() + self._closed = True + + def fetch( + self, request: Union["HTTPRequest", str], **kwargs: Any + ) -> "HTTPResponse": + """Executes a request, returning an `HTTPResponse`. + + The request may be either a string URL or an `HTTPRequest` object. + If it is a string, we construct an `HTTPRequest` using any additional + kwargs: ``HTTPRequest(request, **kwargs)`` + + If an error occurs during the fetch, we raise an `HTTPError` unless + the ``raise_error`` keyword argument is set to False. + """ + response = self._io_loop.run_sync( + functools.partial(self._async_client.fetch, request, **kwargs) + ) + return response + + +class AsyncHTTPClient(Configurable): + """An non-blocking HTTP client. + + Example usage:: + + async def f(): + http_client = AsyncHTTPClient() + try: + response = await http_client.fetch("http://www.google.com") + except Exception as e: + print("Error: %s" % e) + else: + print(response.body) + + The constructor for this class is magic in several respects: It + actually creates an instance of an implementation-specific + subclass, and instances are reused as a kind of pseudo-singleton + (one per `.IOLoop`). The keyword argument ``force_instance=True`` + can be used to suppress this singleton behavior. Unless + ``force_instance=True`` is used, no arguments should be passed to + the `AsyncHTTPClient` constructor. The implementation subclass as + well as arguments to its constructor can be set with the static + method `configure()` + + All `AsyncHTTPClient` implementations support a ``defaults`` + keyword argument, which can be used to set default values for + `HTTPRequest` attributes. For example:: + + AsyncHTTPClient.configure( + None, defaults=dict(user_agent="MyUserAgent")) + # or with force_instance: + client = AsyncHTTPClient(force_instance=True, + defaults=dict(user_agent="MyUserAgent")) + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + + """ + + _instance_cache = None # type: Dict[IOLoop, AsyncHTTPClient] + + @classmethod + def configurable_base(cls) -> Type[Configurable]: + return AsyncHTTPClient + + @classmethod + def configurable_default(cls) -> Type[Configurable]: + from tornado.simple_httpclient import SimpleAsyncHTTPClient + + return SimpleAsyncHTTPClient + + @classmethod + def _async_clients(cls) -> Dict[IOLoop, "AsyncHTTPClient"]: + attr_name = "_async_client_dict_" + cls.__name__ + if not hasattr(cls, attr_name): + setattr(cls, attr_name, weakref.WeakKeyDictionary()) + return getattr(cls, attr_name) + + def __new__(cls, force_instance: bool = False, **kwargs: Any) -> "AsyncHTTPClient": + io_loop = IOLoop.current() + if force_instance: + instance_cache = None + else: + instance_cache = cls._async_clients() + if instance_cache is not None and io_loop in instance_cache: + return instance_cache[io_loop] + instance = super(AsyncHTTPClient, cls).__new__(cls, **kwargs) # type: ignore + # Make sure the instance knows which cache to remove itself from. + # It can't simply call _async_clients() because we may be in + # __new__(AsyncHTTPClient) but instance.__class__ may be + # SimpleAsyncHTTPClient. + instance._instance_cache = instance_cache + if instance_cache is not None: + instance_cache[instance.io_loop] = instance + return instance + + def initialize(self, defaults: Dict[str, Any] = None) -> None: + self.io_loop = IOLoop.current() + self.defaults = dict(HTTPRequest._DEFAULTS) + if defaults is not None: + self.defaults.update(defaults) + self._closed = False + + def close(self) -> None: + """Destroys this HTTP client, freeing any file descriptors used. + + This method is **not needed in normal use** due to the way + that `AsyncHTTPClient` objects are transparently reused. + ``close()`` is generally only necessary when either the + `.IOLoop` is also being closed, or the ``force_instance=True`` + argument was used when creating the `AsyncHTTPClient`. + + No other methods may be called on the `AsyncHTTPClient` after + ``close()``. + + """ + if self._closed: + return + self._closed = True + if self._instance_cache is not None: + cached_val = self._instance_cache.pop(self.io_loop, None) + # If there's an object other than self in the instance + # cache for our IOLoop, something has gotten mixed up. A + # value of None appears to be possible when this is called + # from a destructor (HTTPClient.__del__) as the weakref + # gets cleared before the destructor runs. + if cached_val is not None and cached_val is not self: + raise RuntimeError("inconsistent AsyncHTTPClient cache") + + def fetch( + self, + request: Union[str, "HTTPRequest"], + raise_error: bool = True, + **kwargs: Any + ) -> Awaitable["HTTPResponse"]: + """Executes a request, asynchronously returning an `HTTPResponse`. + + The request may be either a string URL or an `HTTPRequest` object. + If it is a string, we construct an `HTTPRequest` using any additional + kwargs: ``HTTPRequest(request, **kwargs)`` + + This method returns a `.Future` whose result is an + `HTTPResponse`. By default, the ``Future`` will raise an + `HTTPError` if the request returned a non-200 response code + (other errors may also be raised if the server could not be + contacted). Instead, if ``raise_error`` is set to False, the + response will always be returned regardless of the response + code. + + If a ``callback`` is given, it will be invoked with the `HTTPResponse`. + In the callback interface, `HTTPError` is not automatically raised. + Instead, you must check the response's ``error`` attribute or + call its `~HTTPResponse.rethrow` method. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + + The ``raise_error=False`` argument only affects the + `HTTPError` raised when a non-200 response code is used, + instead of suppressing all errors. + """ + if self._closed: + raise RuntimeError("fetch() called on closed AsyncHTTPClient") + if not isinstance(request, HTTPRequest): + request = HTTPRequest(url=request, **kwargs) + else: + if kwargs: + raise ValueError( + "kwargs can't be used if request is an HTTPRequest object" + ) + # We may modify this (to add Host, Accept-Encoding, etc), + # so make sure we don't modify the caller's object. This is also + # where normal dicts get converted to HTTPHeaders objects. + request.headers = httputil.HTTPHeaders(request.headers) + request_proxy = _RequestProxy(request, self.defaults) + future = Future() # type: Future[HTTPResponse] + + def handle_response(response: "HTTPResponse") -> None: + if response.error: + if raise_error or not response._error_is_response_code: + future_set_exception_unless_cancelled(future, response.error) + return + future_set_result_unless_cancelled(future, response) + + self.fetch_impl(cast(HTTPRequest, request_proxy), handle_response) + return future + + def fetch_impl( + self, request: "HTTPRequest", callback: Callable[["HTTPResponse"], None] + ) -> None: + raise NotImplementedError() + + @classmethod + def configure( + cls, impl: "Union[None, str, Type[Configurable]]", **kwargs: Any + ) -> None: + """Configures the `AsyncHTTPClient` subclass to use. + + ``AsyncHTTPClient()`` actually creates an instance of a subclass. + This method may be called with either a class object or the + fully-qualified name of such a class (or ``None`` to use the default, + ``SimpleAsyncHTTPClient``) + + If additional keyword arguments are given, they will be passed + to the constructor of each subclass instance created. The + keyword argument ``max_clients`` determines the maximum number + of simultaneous `~AsyncHTTPClient.fetch()` operations that can + execute in parallel on each `.IOLoop`. Additional arguments + may be supported depending on the implementation class in use. + + Example:: + + AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient") + """ + super(AsyncHTTPClient, cls).configure(impl, **kwargs) + + +class HTTPRequest(object): + """HTTP client request object.""" + + _headers = None # type: Union[Dict[str, str], httputil.HTTPHeaders] + + # Default values for HTTPRequest parameters. + # Merged with the values on the request object by AsyncHTTPClient + # implementations. + _DEFAULTS = dict( + connect_timeout=20.0, + request_timeout=20.0, + follow_redirects=True, + max_redirects=5, + decompress_response=True, + proxy_password="", + allow_nonstandard_methods=False, + validate_cert=True, + ) + + def __init__( + self, + url: str, + method: str = "GET", + headers: Union[Dict[str, str], httputil.HTTPHeaders] = None, + body: Union[bytes, str] = None, + auth_username: str = None, + auth_password: str = None, + auth_mode: str = None, + connect_timeout: float = None, + request_timeout: float = None, + if_modified_since: Union[float, datetime.datetime] = None, + follow_redirects: bool = None, + max_redirects: int = None, + user_agent: str = None, + use_gzip: bool = None, + network_interface: str = None, + streaming_callback: Callable[[bytes], None] = None, + header_callback: Callable[[str], None] = None, + prepare_curl_callback: Callable[[Any], None] = None, + proxy_host: str = None, + proxy_port: int = None, + proxy_username: str = None, + proxy_password: str = None, + proxy_auth_mode: str = None, + allow_nonstandard_methods: bool = None, + validate_cert: bool = None, + ca_certs: str = None, + allow_ipv6: bool = None, + client_key: str = None, + client_cert: str = None, + body_producer: Callable[[Callable[[bytes], None]], "Future[None]"] = None, + expect_100_continue: bool = False, + decompress_response: bool = None, + ssl_options: Union[Dict[str, Any], ssl.SSLContext] = None, + ) -> None: + r"""All parameters except ``url`` are optional. + + :arg str url: URL to fetch + :arg str method: HTTP method, e.g. "GET" or "POST" + :arg headers: Additional HTTP headers to pass on the request + :type headers: `~tornado.httputil.HTTPHeaders` or `dict` + :arg body: HTTP request body as a string (byte or unicode; if unicode + the utf-8 encoding will be used) + :arg body_producer: Callable used for lazy/asynchronous request bodies. + It is called with one argument, a ``write`` function, and should + return a `.Future`. It should call the write function with new + data as it becomes available. The write function returns a + `.Future` which can be used for flow control. + Only one of ``body`` and ``body_producer`` may + be specified. ``body_producer`` is not supported on + ``curl_httpclient``. When using ``body_producer`` it is recommended + to pass a ``Content-Length`` in the headers as otherwise chunked + encoding will be used, and many servers do not support chunked + encoding on requests. New in Tornado 4.0 + :arg str auth_username: Username for HTTP authentication + :arg str auth_password: Password for HTTP authentication + :arg str auth_mode: Authentication mode; default is "basic". + Allowed values are implementation-defined; ``curl_httpclient`` + supports "basic" and "digest"; ``simple_httpclient`` only supports + "basic" + :arg float connect_timeout: Timeout for initial connection in seconds, + default 20 seconds + :arg float request_timeout: Timeout for entire request in seconds, + default 20 seconds + :arg if_modified_since: Timestamp for ``If-Modified-Since`` header + :type if_modified_since: `datetime` or `float` + :arg bool follow_redirects: Should redirects be followed automatically + or return the 3xx response? Default True. + :arg int max_redirects: Limit for ``follow_redirects``, default 5. + :arg str user_agent: String to send as ``User-Agent`` header + :arg bool decompress_response: Request a compressed response from + the server and decompress it after downloading. Default is True. + New in Tornado 4.0. + :arg bool use_gzip: Deprecated alias for ``decompress_response`` + since Tornado 4.0. + :arg str network_interface: Network interface or source IP to use for request. + See ``curl_httpclient`` note below. + :arg collections.abc.Callable streaming_callback: If set, ``streaming_callback`` will + be run with each chunk of data as it is received, and + ``HTTPResponse.body`` and ``HTTPResponse.buffer`` will be empty in + the final response. + :arg collections.abc.Callable header_callback: If set, ``header_callback`` will + be run with each header line as it is received (including the + first line, e.g. ``HTTP/1.0 200 OK\r\n``, and a final line + containing only ``\r\n``. All lines include the trailing newline + characters). ``HTTPResponse.headers`` will be empty in the final + response. This is most useful in conjunction with + ``streaming_callback``, because it's the only way to get access to + header data while the request is in progress. + :arg collections.abc.Callable prepare_curl_callback: If set, will be called with + a ``pycurl.Curl`` object to allow the application to make additional + ``setopt`` calls. + :arg str proxy_host: HTTP proxy hostname. To use proxies, + ``proxy_host`` and ``proxy_port`` must be set; ``proxy_username``, + ``proxy_pass`` and ``proxy_auth_mode`` are optional. Proxies are + currently only supported with ``curl_httpclient``. + :arg int proxy_port: HTTP proxy port + :arg str proxy_username: HTTP proxy username + :arg str proxy_password: HTTP proxy password + :arg str proxy_auth_mode: HTTP proxy Authentication mode; + default is "basic". supports "basic" and "digest" + :arg bool allow_nonstandard_methods: Allow unknown values for ``method`` + argument? Default is False. + :arg bool validate_cert: For HTTPS requests, validate the server's + certificate? Default is True. + :arg str ca_certs: filename of CA certificates in PEM format, + or None to use defaults. See note below when used with + ``curl_httpclient``. + :arg str client_key: Filename for client SSL key, if any. See + note below when used with ``curl_httpclient``. + :arg str client_cert: Filename for client SSL certificate, if any. + See note below when used with ``curl_httpclient``. + :arg ssl.SSLContext ssl_options: `ssl.SSLContext` object for use in + ``simple_httpclient`` (unsupported by ``curl_httpclient``). + Overrides ``validate_cert``, ``ca_certs``, ``client_key``, + and ``client_cert``. + :arg bool allow_ipv6: Use IPv6 when available? Default is True. + :arg bool expect_100_continue: If true, send the + ``Expect: 100-continue`` header and wait for a continue response + before sending the request body. Only supported with + ``simple_httpclient``. + + .. note:: + + When using ``curl_httpclient`` certain options may be + inherited by subsequent fetches because ``pycurl`` does + not allow them to be cleanly reset. This applies to the + ``ca_certs``, ``client_key``, ``client_cert``, and + ``network_interface`` arguments. If you use these + options, you should pass them on every request (you don't + have to always use the same values, but it's not possible + to mix requests that specify these options with ones that + use the defaults). + + .. versionadded:: 3.1 + The ``auth_mode`` argument. + + .. versionadded:: 4.0 + The ``body_producer`` and ``expect_100_continue`` arguments. + + .. versionadded:: 4.2 + The ``ssl_options`` argument. + + .. versionadded:: 4.5 + The ``proxy_auth_mode`` argument. + """ + # Note that some of these attributes go through property setters + # defined below. + self.headers = headers + if if_modified_since: + self.headers["If-Modified-Since"] = httputil.format_timestamp( + if_modified_since + ) + self.proxy_host = proxy_host + self.proxy_port = proxy_port + self.proxy_username = proxy_username + self.proxy_password = proxy_password + self.proxy_auth_mode = proxy_auth_mode + self.url = url + self.method = method + self.body = body + self.body_producer = body_producer + self.auth_username = auth_username + self.auth_password = auth_password + self.auth_mode = auth_mode + self.connect_timeout = connect_timeout + self.request_timeout = request_timeout + self.follow_redirects = follow_redirects + self.max_redirects = max_redirects + self.user_agent = user_agent + if decompress_response is not None: + self.decompress_response = decompress_response # type: Optional[bool] + else: + self.decompress_response = use_gzip + self.network_interface = network_interface + self.streaming_callback = streaming_callback + self.header_callback = header_callback + self.prepare_curl_callback = prepare_curl_callback + self.allow_nonstandard_methods = allow_nonstandard_methods + self.validate_cert = validate_cert + self.ca_certs = ca_certs + self.allow_ipv6 = allow_ipv6 + self.client_key = client_key + self.client_cert = client_cert + self.ssl_options = ssl_options + self.expect_100_continue = expect_100_continue + self.start_time = time.time() + + @property + def headers(self) -> httputil.HTTPHeaders: + # TODO: headers may actually be a plain dict until fairly late in + # the process (AsyncHTTPClient.fetch), but practically speaking, + # whenever the property is used they're already HTTPHeaders. + return self._headers # type: ignore + + @headers.setter + def headers(self, value: Union[Dict[str, str], httputil.HTTPHeaders]) -> None: + if value is None: + self._headers = httputil.HTTPHeaders() + else: + self._headers = value # type: ignore + + @property + def body(self) -> bytes: + return self._body + + @body.setter + def body(self, value: Union[bytes, str]) -> None: + self._body = utf8(value) + + +class HTTPResponse(object): + """HTTP Response object. + + Attributes: + + * ``request``: HTTPRequest object + + * ``code``: numeric HTTP status code, e.g. 200 or 404 + + * ``reason``: human-readable reason phrase describing the status code + + * ``headers``: `tornado.httputil.HTTPHeaders` object + + * ``effective_url``: final location of the resource after following any + redirects + + * ``buffer``: ``cStringIO`` object for response body + + * ``body``: response body as bytes (created on demand from ``self.buffer``) + + * ``error``: Exception object, if any + + * ``request_time``: seconds from request start to finish. Includes all + network operations from DNS resolution to receiving the last byte of + data. Does not include time spent in the queue (due to the + ``max_clients`` option). If redirects were followed, only includes + the final request. + + * ``start_time``: Time at which the HTTP operation started, based on + `time.time` (not the monotonic clock used by `.IOLoop.time`). May + be ``None`` if the request timed out while in the queue. + + * ``time_info``: dictionary of diagnostic timing information from the + request. Available data are subject to change, but currently uses timings + available from http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html, + plus ``queue``, which is the delay (if any) introduced by waiting for + a slot under `AsyncHTTPClient`'s ``max_clients`` setting. + + .. versionadded:: 5.1 + + Added the ``start_time`` attribute. + + .. versionchanged:: 5.1 + + The ``request_time`` attribute previously included time spent in the queue + for ``simple_httpclient``, but not in ``curl_httpclient``. Now queueing time + is excluded in both implementations. ``request_time`` is now more accurate for + ``curl_httpclient`` because it uses a monotonic clock when available. + """ + + # I'm not sure why these don't get type-inferred from the references in __init__. + error = None # type: Optional[BaseException] + _error_is_response_code = False + request = None # type: HTTPRequest + + def __init__( + self, + request: HTTPRequest, + code: int, + headers: httputil.HTTPHeaders = None, + buffer: BytesIO = None, + effective_url: str = None, + error: BaseException = None, + request_time: float = None, + time_info: Dict[str, float] = None, + reason: str = None, + start_time: float = None, + ) -> None: + if isinstance(request, _RequestProxy): + self.request = request.request + else: + self.request = request + self.code = code + self.reason = reason or httputil.responses.get(code, "Unknown") + if headers is not None: + self.headers = headers + else: + self.headers = httputil.HTTPHeaders() + self.buffer = buffer + self._body = None # type: Optional[bytes] + if effective_url is None: + self.effective_url = request.url + else: + self.effective_url = effective_url + self._error_is_response_code = False + if error is None: + if self.code < 200 or self.code >= 300: + self._error_is_response_code = True + self.error = HTTPError(self.code, message=self.reason, response=self) + else: + self.error = None + else: + self.error = error + self.start_time = start_time + self.request_time = request_time + self.time_info = time_info or {} + + @property + def body(self) -> bytes: + if self.buffer is None: + return b"" + elif self._body is None: + self._body = self.buffer.getvalue() + + return self._body + + def rethrow(self) -> None: + """If there was an error on the request, raise an `HTTPError`.""" + if self.error: + raise self.error + + def __repr__(self) -> str: + args = ",".join("%s=%r" % i for i in sorted(self.__dict__.items())) + return "%s(%s)" % (self.__class__.__name__, args) + + +class HTTPClientError(Exception): + """Exception thrown for an unsuccessful HTTP request. + + Attributes: + + * ``code`` - HTTP error integer error code, e.g. 404. Error code 599 is + used when no HTTP response was received, e.g. for a timeout. + + * ``response`` - `HTTPResponse` object, if any. + + Note that if ``follow_redirects`` is False, redirects become HTTPErrors, + and you can look at ``error.response.headers['Location']`` to see the + destination of the redirect. + + .. versionchanged:: 5.1 + + Renamed from ``HTTPError`` to ``HTTPClientError`` to avoid collisions with + `tornado.web.HTTPError`. The name ``tornado.httpclient.HTTPError`` remains + as an alias. + """ + + def __init__( + self, code: int, message: str = None, response: HTTPResponse = None + ) -> None: + self.code = code + self.message = message or httputil.responses.get(code, "Unknown") + self.response = response + super(HTTPClientError, self).__init__(code, message, response) + + def __str__(self) -> str: + return "HTTP %d: %s" % (self.code, self.message) + + # There is a cyclic reference between self and self.response, + # which breaks the default __repr__ implementation. + # (especially on pypy, which doesn't have the same recursion + # detection as cpython). + __repr__ = __str__ + + +HTTPError = HTTPClientError + + +class _RequestProxy(object): + """Combines an object with a dictionary of defaults. + + Used internally by AsyncHTTPClient implementations. + """ + + def __init__( + self, request: HTTPRequest, defaults: Optional[Dict[str, Any]] + ) -> None: + self.request = request + self.defaults = defaults + + def __getattr__(self, name: str) -> Any: + request_attr = getattr(self.request, name) + if request_attr is not None: + return request_attr + elif self.defaults is not None: + return self.defaults.get(name, None) + else: + return None + + +def main() -> None: + from tornado.options import define, options, parse_command_line + + define("print_headers", type=bool, default=False) + define("print_body", type=bool, default=True) + define("follow_redirects", type=bool, default=True) + define("validate_cert", type=bool, default=True) + define("proxy_host", type=str) + define("proxy_port", type=int) + args = parse_command_line() + client = HTTPClient() + for arg in args: + try: + response = client.fetch( + arg, + follow_redirects=options.follow_redirects, + validate_cert=options.validate_cert, + proxy_host=options.proxy_host, + proxy_port=options.proxy_port, + ) + except HTTPError as e: + if e.response is not None: + response = e.response + else: + raise + if options.print_headers: + print(response.headers) + if options.print_body: + print(native_str(response.body)) + client.close() + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.7/site-packages/tornado/httpserver.py b/venv/lib/python3.7/site-packages/tornado/httpserver.py new file mode 100644 index 0000000..8044a4f --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/httpserver.py @@ -0,0 +1,398 @@ +# +# Copyright 2009 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. + +"""A non-blocking, single-threaded HTTP server. + +Typical applications have little direct interaction with the `HTTPServer` +class except to start a server at the beginning of the process +(and even that is often done indirectly via `tornado.web.Application.listen`). + +.. versionchanged:: 4.0 + + The ``HTTPRequest`` class that used to live in this module has been moved + to `tornado.httputil.HTTPServerRequest`. The old name remains as an alias. +""" + +import socket +import ssl + +from tornado.escape import native_str +from tornado.http1connection import HTTP1ServerConnection, HTTP1ConnectionParameters +from tornado import httputil +from tornado import iostream +from tornado import netutil +from tornado.tcpserver import TCPServer +from tornado.util import Configurable + +import typing +from typing import Union, Any, Dict, Callable, List, Type, Tuple, Optional, Awaitable + +if typing.TYPE_CHECKING: + from typing import Set # noqa: F401 + + +class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate): + r"""A non-blocking, single-threaded HTTP server. + + A server is defined by a subclass of `.HTTPServerConnectionDelegate`, + or, for backwards compatibility, a callback that takes an + `.HTTPServerRequest` as an argument. The delegate is usually a + `tornado.web.Application`. + + `HTTPServer` supports keep-alive connections by default + (automatically for HTTP/1.1, or for HTTP/1.0 when the client + requests ``Connection: keep-alive``). + + If ``xheaders`` is ``True``, we support the + ``X-Real-Ip``/``X-Forwarded-For`` and + ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the + remote IP and URI scheme/protocol for all requests. These headers + are useful when running Tornado behind a reverse proxy or load + balancer. The ``protocol`` argument can also be set to ``https`` + if Tornado is run behind an SSL-decoding proxy that does not set one of + the supported ``xheaders``. + + By default, when parsing the ``X-Forwarded-For`` header, Tornado will + select the last (i.e., the closest) address on the list of hosts as the + remote host IP address. To select the next server in the chain, a list of + trusted downstream hosts may be passed as the ``trusted_downstream`` + argument. These hosts will be skipped when parsing the ``X-Forwarded-For`` + header. + + To make this server serve SSL traffic, send the ``ssl_options`` keyword + argument with an `ssl.SSLContext` object. For compatibility with older + versions of Python ``ssl_options`` may also be a dictionary of keyword + arguments for the `ssl.wrap_socket` method.:: + + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"), + os.path.join(data_dir, "mydomain.key")) + HTTPServer(application, ssl_options=ssl_ctx) + + `HTTPServer` initialization follows one of three patterns (the + initialization methods are defined on `tornado.tcpserver.TCPServer`): + + 1. `~tornado.tcpserver.TCPServer.listen`: simple single-process:: + + server = HTTPServer(app) + server.listen(8888) + IOLoop.current().start() + + In many cases, `tornado.web.Application.listen` can be used to avoid + the need to explicitly create the `HTTPServer`. + + 2. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`: + simple multi-process:: + + server = HTTPServer(app) + server.bind(8888) + server.start(0) # Forks multiple sub-processes + IOLoop.current().start() + + When using this interface, an `.IOLoop` must *not* be passed + to the `HTTPServer` constructor. `~.TCPServer.start` will always start + the server on the default singleton `.IOLoop`. + + 3. `~tornado.tcpserver.TCPServer.add_sockets`: advanced multi-process:: + + sockets = tornado.netutil.bind_sockets(8888) + tornado.process.fork_processes(0) + server = HTTPServer(app) + server.add_sockets(sockets) + IOLoop.current().start() + + The `~.TCPServer.add_sockets` interface is more complicated, + but it can be used with `tornado.process.fork_processes` to + give you more flexibility in when the fork happens. + `~.TCPServer.add_sockets` can also be used in single-process + servers if you want to create your listening sockets in some + way other than `tornado.netutil.bind_sockets`. + + .. versionchanged:: 4.0 + Added ``decompress_request``, ``chunk_size``, ``max_header_size``, + ``idle_connection_timeout``, ``body_timeout``, ``max_body_size`` + arguments. Added support for `.HTTPServerConnectionDelegate` + instances as ``request_callback``. + + .. versionchanged:: 4.1 + `.HTTPServerConnectionDelegate.start_request` is now called with + two arguments ``(server_conn, request_conn)`` (in accordance with the + documentation) instead of one ``(request_conn)``. + + .. versionchanged:: 4.2 + `HTTPServer` is now a subclass of `tornado.util.Configurable`. + + .. versionchanged:: 4.5 + Added the ``trusted_downstream`` argument. + + .. versionchanged:: 5.0 + The ``io_loop`` argument has been removed. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + # Ignore args to __init__; real initialization belongs in + # initialize since we're Configurable. (there's something + # weird in initialization order between this class, + # Configurable, and TCPServer so we can't leave __init__ out + # completely) + pass + + def initialize( + self, + request_callback: Union[ + httputil.HTTPServerConnectionDelegate, + Callable[[httputil.HTTPServerRequest], None], + ], + no_keep_alive: bool = False, + xheaders: bool = False, + ssl_options: Union[Dict[str, Any], ssl.SSLContext] = None, + protocol: str = None, + decompress_request: bool = False, + chunk_size: int = None, + max_header_size: int = None, + idle_connection_timeout: float = None, + body_timeout: float = None, + max_body_size: int = None, + max_buffer_size: int = None, + trusted_downstream: List[str] = None, + ) -> None: + # This method's signature is not extracted with autodoc + # because we want its arguments to appear on the class + # constructor. When changing this signature, also update the + # copy in httpserver.rst. + self.request_callback = request_callback + self.xheaders = xheaders + self.protocol = protocol + self.conn_params = HTTP1ConnectionParameters( + decompress=decompress_request, + chunk_size=chunk_size, + max_header_size=max_header_size, + header_timeout=idle_connection_timeout or 3600, + max_body_size=max_body_size, + body_timeout=body_timeout, + no_keep_alive=no_keep_alive, + ) + TCPServer.__init__( + self, + ssl_options=ssl_options, + max_buffer_size=max_buffer_size, + read_chunk_size=chunk_size, + ) + self._connections = set() # type: Set[HTTP1ServerConnection] + self.trusted_downstream = trusted_downstream + + @classmethod + def configurable_base(cls) -> Type[Configurable]: + return HTTPServer + + @classmethod + def configurable_default(cls) -> Type[Configurable]: + return HTTPServer + + async def close_all_connections(self) -> None: + """Close all open connections and asynchronously wait for them to finish. + + This method is used in combination with `~.TCPServer.stop` to + support clean shutdowns (especially for unittests). Typical + usage would call ``stop()`` first to stop accepting new + connections, then ``await close_all_connections()`` to wait for + existing connections to finish. + + This method does not currently close open websocket connections. + + Note that this method is a coroutine and must be caled with ``await``. + + """ + while self._connections: + # Peek at an arbitrary element of the set + conn = next(iter(self._connections)) + await conn.close() + + def handle_stream(self, stream: iostream.IOStream, address: Tuple) -> None: + context = _HTTPRequestContext( + stream, address, self.protocol, self.trusted_downstream + ) + conn = HTTP1ServerConnection(stream, self.conn_params, context) + self._connections.add(conn) + conn.start_serving(self) + + def start_request( + self, server_conn: object, request_conn: httputil.HTTPConnection + ) -> httputil.HTTPMessageDelegate: + if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate): + delegate = self.request_callback.start_request(server_conn, request_conn) + else: + delegate = _CallableAdapter(self.request_callback, request_conn) + + if self.xheaders: + delegate = _ProxyAdapter(delegate, request_conn) + + return delegate + + def on_close(self, server_conn: object) -> None: + self._connections.remove(typing.cast(HTTP1ServerConnection, server_conn)) + + +class _CallableAdapter(httputil.HTTPMessageDelegate): + def __init__( + self, + request_callback: Callable[[httputil.HTTPServerRequest], None], + request_conn: httputil.HTTPConnection, + ) -> None: + self.connection = request_conn + self.request_callback = request_callback + self.request = None # type: Optional[httputil.HTTPServerRequest] + self.delegate = None + self._chunks = [] # type: List[bytes] + + def headers_received( + self, + start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], + headers: httputil.HTTPHeaders, + ) -> Optional[Awaitable[None]]: + self.request = httputil.HTTPServerRequest( + connection=self.connection, + start_line=typing.cast(httputil.RequestStartLine, start_line), + headers=headers, + ) + return None + + def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: + self._chunks.append(chunk) + return None + + def finish(self) -> None: + assert self.request is not None + self.request.body = b"".join(self._chunks) + self.request._parse_body() + self.request_callback(self.request) + + def on_connection_close(self) -> None: + del self._chunks + + +class _HTTPRequestContext(object): + def __init__( + self, + stream: iostream.IOStream, + address: Tuple, + protocol: Optional[str], + trusted_downstream: List[str] = None, + ) -> None: + self.address = address + # Save the socket's address family now so we know how to + # interpret self.address even after the stream is closed + # and its socket attribute replaced with None. + if stream.socket is not None: + self.address_family = stream.socket.family + else: + self.address_family = None + # In HTTPServerRequest we want an IP, not a full socket address. + if ( + self.address_family in (socket.AF_INET, socket.AF_INET6) + and address is not None + ): + self.remote_ip = address[0] + else: + # Unix (or other) socket; fake the remote address. + self.remote_ip = "0.0.0.0" + if protocol: + self.protocol = protocol + elif isinstance(stream, iostream.SSLIOStream): + self.protocol = "https" + else: + self.protocol = "http" + self._orig_remote_ip = self.remote_ip + self._orig_protocol = self.protocol + self.trusted_downstream = set(trusted_downstream or []) + + def __str__(self) -> str: + if self.address_family in (socket.AF_INET, socket.AF_INET6): + return self.remote_ip + elif isinstance(self.address, bytes): + # Python 3 with the -bb option warns about str(bytes), + # so convert it explicitly. + # Unix socket addresses are str on mac but bytes on linux. + return native_str(self.address) + else: + return str(self.address) + + def _apply_xheaders(self, headers: httputil.HTTPHeaders) -> None: + """Rewrite the ``remote_ip`` and ``protocol`` fields.""" + # Squid uses X-Forwarded-For, others use X-Real-Ip + ip = headers.get("X-Forwarded-For", self.remote_ip) + # Skip trusted downstream hosts in X-Forwarded-For list + for ip in (cand.strip() for cand in reversed(ip.split(","))): + if ip not in self.trusted_downstream: + break + ip = headers.get("X-Real-Ip", ip) + if netutil.is_valid_ip(ip): + self.remote_ip = ip + # AWS uses X-Forwarded-Proto + proto_header = headers.get( + "X-Scheme", headers.get("X-Forwarded-Proto", self.protocol) + ) + if proto_header: + # use only the last proto entry if there is more than one + # TODO: support trusting mutiple layers of proxied protocol + proto_header = proto_header.split(",")[-1].strip() + if proto_header in ("http", "https"): + self.protocol = proto_header + + def _unapply_xheaders(self) -> None: + """Undo changes from `_apply_xheaders`. + + Xheaders are per-request so they should not leak to the next + request on the same connection. + """ + self.remote_ip = self._orig_remote_ip + self.protocol = self._orig_protocol + + +class _ProxyAdapter(httputil.HTTPMessageDelegate): + def __init__( + self, + delegate: httputil.HTTPMessageDelegate, + request_conn: httputil.HTTPConnection, + ) -> None: + self.connection = request_conn + self.delegate = delegate + + def headers_received( + self, + start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], + headers: httputil.HTTPHeaders, + ) -> Optional[Awaitable[None]]: + # TODO: either make context an official part of the + # HTTPConnection interface or figure out some other way to do this. + self.connection.context._apply_xheaders(headers) # type: ignore + return self.delegate.headers_received(start_line, headers) + + def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: + return self.delegate.data_received(chunk) + + def finish(self) -> None: + self.delegate.finish() + self._cleanup() + + def on_connection_close(self) -> None: + self.delegate.on_connection_close() + self._cleanup() + + def _cleanup(self) -> None: + self.connection.context._unapply_xheaders() # type: ignore + + +HTTPRequest = httputil.HTTPServerRequest diff --git a/venv/lib/python3.7/site-packages/tornado/httputil.py b/venv/lib/python3.7/site-packages/tornado/httputil.py new file mode 100644 index 0000000..835327d --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/httputil.py @@ -0,0 +1,1144 @@ +# +# Copyright 2009 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. + +"""HTTP utility code shared by clients and servers. + +This module also defines the `HTTPServerRequest` class which is exposed +via `tornado.web.RequestHandler.request`. +""" + +import calendar +import collections +import copy +import datetime +import email.utils +from http.client import responses +import http.cookies +import re +from ssl import SSLError +import time +import unicodedata +from urllib.parse import urlencode, urlparse, urlunparse, parse_qsl + +from tornado.escape import native_str, parse_qs_bytes, utf8 +from tornado.log import gen_log +from tornado.util import ObjectDict, unicode_type + + +# responses is unused in this file, but we re-export it to other files. +# Reference it so pyflakes doesn't complain. +responses + +import typing +from typing import ( + Tuple, + Iterable, + List, + Mapping, + Iterator, + Dict, + Union, + Optional, + Awaitable, + Generator, + AnyStr, +) + +if typing.TYPE_CHECKING: + from typing import Deque # noqa: F401 + from asyncio import Future # noqa: F401 + import unittest # noqa: F401 + + +# RFC 7230 section 3.5: a recipient MAY recognize a single LF as a line +# terminator and ignore any preceding CR. +_CRLF_RE = re.compile(r"\r?\n") + + +class _NormalizedHeaderCache(dict): + """Dynamic cached mapping of header names to Http-Header-Case. + + Implemented as a dict subclass so that cache hits are as fast as a + normal dict lookup, without the overhead of a python function + call. + + >>> normalized_headers = _NormalizedHeaderCache(10) + >>> normalized_headers["coNtent-TYPE"] + 'Content-Type' + """ + + def __init__(self, size: int) -> None: + super(_NormalizedHeaderCache, self).__init__() + self.size = size + self.queue = collections.deque() # type: Deque[str] + + def __missing__(self, key: str) -> str: + normalized = "-".join([w.capitalize() for w in key.split("-")]) + self[key] = normalized + self.queue.append(key) + if len(self.queue) > self.size: + # Limit the size of the cache. LRU would be better, but this + # simpler approach should be fine. In Python 2.7+ we could + # use OrderedDict (or in 3.2+, @functools.lru_cache). + old_key = self.queue.popleft() + del self[old_key] + return normalized + + +_normalized_headers = _NormalizedHeaderCache(1000) + + +class HTTPHeaders(collections.abc.MutableMapping): + """A dictionary that maintains ``Http-Header-Case`` for all keys. + + Supports multiple values per key via a pair of new methods, + `add()` and `get_list()`. The regular dictionary interface + returns a single value per key, with multiple values joined by a + comma. + + >>> h = HTTPHeaders({"content-type": "text/html"}) + >>> list(h.keys()) + ['Content-Type'] + >>> h["Content-Type"] + 'text/html' + + >>> h.add("Set-Cookie", "A=B") + >>> h.add("Set-Cookie", "C=D") + >>> h["set-cookie"] + 'A=B,C=D' + >>> h.get_list("set-cookie") + ['A=B', 'C=D'] + + >>> for (k,v) in sorted(h.get_all()): + ... print('%s: %s' % (k,v)) + ... + Content-Type: text/html + Set-Cookie: A=B + Set-Cookie: C=D + """ + + @typing.overload + def __init__(self, __arg: Mapping[str, List[str]]) -> None: + pass + + @typing.overload # noqa: F811 + def __init__(self, __arg: Mapping[str, str]) -> None: + pass + + @typing.overload # noqa: F811 + def __init__(self, *args: Tuple[str, str]) -> None: + pass + + @typing.overload # noqa: F811 + def __init__(self, **kwargs: str) -> None: + pass + + def __init__(self, *args: typing.Any, **kwargs: str) -> None: # noqa: F811 + self._dict = {} # type: typing.Dict[str, str] + self._as_list = {} # type: typing.Dict[str, typing.List[str]] + self._last_key = None + if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], HTTPHeaders): + # Copy constructor + for k, v in args[0].get_all(): + self.add(k, v) + else: + # Dict-style initialization + self.update(*args, **kwargs) + + # new public methods + + def add(self, name: str, value: str) -> None: + """Adds a new value for the given key.""" + norm_name = _normalized_headers[name] + self._last_key = norm_name + if norm_name in self: + self._dict[norm_name] = ( + native_str(self[norm_name]) + "," + native_str(value) + ) + self._as_list[norm_name].append(value) + else: + self[norm_name] = value + + def get_list(self, name: str) -> List[str]: + """Returns all values for the given header as a list.""" + norm_name = _normalized_headers[name] + return self._as_list.get(norm_name, []) + + def get_all(self) -> Iterable[Tuple[str, str]]: + """Returns an iterable of all (name, value) pairs. + + If a header has multiple values, multiple pairs will be + returned with the same name. + """ + for name, values in self._as_list.items(): + for value in values: + yield (name, value) + + def parse_line(self, line: str) -> None: + """Updates the dictionary with a single header line. + + >>> h = HTTPHeaders() + >>> h.parse_line("Content-Type: text/html") + >>> h.get('content-type') + 'text/html' + """ + if line[0].isspace(): + # continuation of a multi-line header + if self._last_key is None: + raise HTTPInputError("first header line cannot start with whitespace") + new_part = " " + line.lstrip() + self._as_list[self._last_key][-1] += new_part + self._dict[self._last_key] += new_part + else: + try: + name, value = line.split(":", 1) + except ValueError: + raise HTTPInputError("no colon in header line") + self.add(name, value.strip()) + + @classmethod + def parse(cls, headers: str) -> "HTTPHeaders": + """Returns a dictionary from HTTP header text. + + >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n") + >>> sorted(h.items()) + [('Content-Length', '42'), ('Content-Type', 'text/html')] + + .. versionchanged:: 5.1 + + Raises `HTTPInputError` on malformed headers instead of a + mix of `KeyError`, and `ValueError`. + + """ + h = cls() + for line in _CRLF_RE.split(headers): + if line: + h.parse_line(line) + return h + + # MutableMapping abstract method implementations. + + def __setitem__(self, name: str, value: str) -> None: + norm_name = _normalized_headers[name] + self._dict[norm_name] = value + self._as_list[norm_name] = [value] + + def __getitem__(self, name: str) -> str: + return self._dict[_normalized_headers[name]] + + def __delitem__(self, name: str) -> None: + norm_name = _normalized_headers[name] + del self._dict[norm_name] + del self._as_list[norm_name] + + def __len__(self) -> int: + return len(self._dict) + + def __iter__(self) -> Iterator[typing.Any]: + return iter(self._dict) + + def copy(self) -> "HTTPHeaders": + # defined in dict but not in MutableMapping. + return HTTPHeaders(self) + + # Use our overridden copy method for the copy.copy module. + # This makes shallow copies one level deeper, but preserves + # the appearance that HTTPHeaders is a single container. + __copy__ = copy + + def __str__(self) -> str: + lines = [] + for name, value in self.get_all(): + lines.append("%s: %s\n" % (name, value)) + return "".join(lines) + + __unicode__ = __str__ + + +class HTTPServerRequest(object): + """A single HTTP request. + + All attributes are type `str` unless otherwise noted. + + .. attribute:: method + + HTTP request method, e.g. "GET" or "POST" + + .. attribute:: uri + + The requested uri. + + .. attribute:: path + + The path portion of `uri` + + .. attribute:: query + + The query portion of `uri` + + .. attribute:: version + + HTTP version specified in request, e.g. "HTTP/1.1" + + .. attribute:: headers + + `.HTTPHeaders` dictionary-like object for request headers. Acts like + a case-insensitive dictionary with additional methods for repeated + headers. + + .. attribute:: body + + Request body, if present, as a byte string. + + .. attribute:: remote_ip + + Client's IP address as a string. If ``HTTPServer.xheaders`` is set, + will pass along the real IP address provided by a load balancer + in the ``X-Real-Ip`` or ``X-Forwarded-For`` header. + + .. versionchanged:: 3.1 + The list format of ``X-Forwarded-For`` is now supported. + + .. attribute:: protocol + + The protocol used, either "http" or "https". If ``HTTPServer.xheaders`` + is set, will pass along the protocol used by a load balancer if + reported via an ``X-Scheme`` header. + + .. attribute:: host + + The requested hostname, usually taken from the ``Host`` header. + + .. attribute:: arguments + + GET/POST arguments are available in the arguments property, which + maps arguments names to lists of values (to support multiple values + for individual names). Names are of type `str`, while arguments + are byte strings. Note that this is different from + `.RequestHandler.get_argument`, which returns argument values as + unicode strings. + + .. attribute:: query_arguments + + Same format as ``arguments``, but contains only arguments extracted + from the query string. + + .. versionadded:: 3.2 + + .. attribute:: body_arguments + + Same format as ``arguments``, but contains only arguments extracted + from the request body. + + .. versionadded:: 3.2 + + .. attribute:: files + + File uploads are available in the files property, which maps file + names to lists of `.HTTPFile`. + + .. attribute:: connection + + An HTTP request is attached to a single HTTP connection, which can + be accessed through the "connection" attribute. Since connections + are typically kept open in HTTP/1.1, multiple requests can be handled + sequentially on a single connection. + + .. versionchanged:: 4.0 + Moved from ``tornado.httpserver.HTTPRequest``. + """ + + path = None # type: str + query = None # type: str + + # HACK: Used for stream_request_body + _body_future = None # type: Future[None] + + def __init__( + self, + method: str = None, + uri: str = None, + version: str = "HTTP/1.0", + headers: HTTPHeaders = None, + body: bytes = None, + host: str = None, + files: Dict[str, List["HTTPFile"]] = None, + connection: "HTTPConnection" = None, + start_line: "RequestStartLine" = None, + server_connection: object = None, + ) -> None: + if start_line is not None: + method, uri, version = start_line + self.method = method + self.uri = uri + self.version = version + self.headers = headers or HTTPHeaders() + self.body = body or b"" + + # set remote IP and protocol + context = getattr(connection, "context", None) + self.remote_ip = getattr(context, "remote_ip", None) + self.protocol = getattr(context, "protocol", "http") + + self.host = host or self.headers.get("Host") or "127.0.0.1" + self.host_name = split_host_and_port(self.host.lower())[0] + self.files = files or {} + self.connection = connection + self.server_connection = server_connection + self._start_time = time.time() + self._finish_time = None + + if uri is not None: + self.path, sep, self.query = uri.partition("?") + self.arguments = parse_qs_bytes(self.query, keep_blank_values=True) + self.query_arguments = copy.deepcopy(self.arguments) + self.body_arguments = {} # type: Dict[str, List[bytes]] + + @property + def cookies(self) -> Dict[str, http.cookies.Morsel]: + """A dictionary of ``http.cookies.Morsel`` objects.""" + if not hasattr(self, "_cookies"): + self._cookies = http.cookies.SimpleCookie() + if "Cookie" in self.headers: + try: + parsed = parse_cookie(self.headers["Cookie"]) + except Exception: + pass + else: + for k, v in parsed.items(): + try: + self._cookies[k] = v + except Exception: + # SimpleCookie imposes some restrictions on keys; + # parse_cookie does not. Discard any cookies + # with disallowed keys. + pass + return self._cookies + + def full_url(self) -> str: + """Reconstructs the full URL for this request.""" + return self.protocol + "://" + self.host + self.uri + + def request_time(self) -> float: + """Returns the amount of time it took for this request to execute.""" + if self._finish_time is None: + return time.time() - self._start_time + else: + return self._finish_time - self._start_time + + def get_ssl_certificate( + self, binary_form: bool = False + ) -> Union[None, Dict, bytes]: + """Returns the client's SSL certificate, if any. + + To use client certificates, the HTTPServer's + `ssl.SSLContext.verify_mode` field must be set, e.g.:: + + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx.load_cert_chain("foo.crt", "foo.key") + ssl_ctx.load_verify_locations("cacerts.pem") + ssl_ctx.verify_mode = ssl.CERT_REQUIRED + server = HTTPServer(app, ssl_options=ssl_ctx) + + By default, the return value is a dictionary (or None, if no + client certificate is present). If ``binary_form`` is true, a + DER-encoded form of the certificate is returned instead. See + SSLSocket.getpeercert() in the standard library for more + details. + http://docs.python.org/library/ssl.html#sslsocket-objects + """ + try: + if self.connection is None: + return None + # TODO: add a method to HTTPConnection for this so it can work with HTTP/2 + return self.connection.stream.socket.getpeercert( # type: ignore + binary_form=binary_form + ) + except SSLError: + return None + + def _parse_body(self) -> None: + parse_body_arguments( + self.headers.get("Content-Type", ""), + self.body, + self.body_arguments, + self.files, + self.headers, + ) + + for k, v in self.body_arguments.items(): + self.arguments.setdefault(k, []).extend(v) + + def __repr__(self) -> str: + attrs = ("protocol", "host", "method", "uri", "version", "remote_ip") + args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs]) + return "%s(%s)" % (self.__class__.__name__, args) + + +class HTTPInputError(Exception): + """Exception class for malformed HTTP requests or responses + from remote sources. + + .. versionadded:: 4.0 + """ + + pass + + +class HTTPOutputError(Exception): + """Exception class for errors in HTTP output. + + .. versionadded:: 4.0 + """ + + pass + + +class HTTPServerConnectionDelegate(object): + """Implement this interface to handle requests from `.HTTPServer`. + + .. versionadded:: 4.0 + """ + + def start_request( + self, server_conn: object, request_conn: "HTTPConnection" + ) -> "HTTPMessageDelegate": + """This method is called by the server when a new request has started. + + :arg server_conn: is an opaque object representing the long-lived + (e.g. tcp-level) connection. + :arg request_conn: is a `.HTTPConnection` object for a single + request/response exchange. + + This method should return a `.HTTPMessageDelegate`. + """ + raise NotImplementedError() + + def on_close(self, server_conn: object) -> None: + """This method is called when a connection has been closed. + + :arg server_conn: is a server connection that has previously been + passed to ``start_request``. + """ + pass + + +class HTTPMessageDelegate(object): + """Implement this interface to handle an HTTP request or response. + + .. versionadded:: 4.0 + """ + + # TODO: genericize this class to avoid exposing the Union. + def headers_received( + self, + start_line: Union["RequestStartLine", "ResponseStartLine"], + headers: HTTPHeaders, + ) -> Optional[Awaitable[None]]: + """Called when the HTTP headers have been received and parsed. + + :arg start_line: a `.RequestStartLine` or `.ResponseStartLine` + depending on whether this is a client or server message. + :arg headers: a `.HTTPHeaders` instance. + + Some `.HTTPConnection` methods can only be called during + ``headers_received``. + + May return a `.Future`; if it does the body will not be read + until it is done. + """ + pass + + def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: + """Called when a chunk of data has been received. + + May return a `.Future` for flow control. + """ + pass + + def finish(self) -> None: + """Called after the last chunk of data has been received.""" + pass + + def on_connection_close(self) -> None: + """Called if the connection is closed without finishing the request. + + If ``headers_received`` is called, either ``finish`` or + ``on_connection_close`` will be called, but not both. + """ + pass + + +class HTTPConnection(object): + """Applications use this interface to write their responses. + + .. versionadded:: 4.0 + """ + + def write_headers( + self, + start_line: Union["RequestStartLine", "ResponseStartLine"], + headers: HTTPHeaders, + chunk: bytes = None, + ) -> "Future[None]": + """Write an HTTP header block. + + :arg start_line: a `.RequestStartLine` or `.ResponseStartLine`. + :arg headers: a `.HTTPHeaders` instance. + :arg chunk: the first (optional) chunk of data. This is an optimization + so that small responses can be written in the same call as their + headers. + + The ``version`` field of ``start_line`` is ignored. + + Returns a future for flow control. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. + """ + raise NotImplementedError() + + def write(self, chunk: bytes) -> "Future[None]": + """Writes a chunk of body data. + + Returns a future for flow control. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. + """ + raise NotImplementedError() + + def finish(self) -> None: + """Indicates that the last body data has been written. + """ + raise NotImplementedError() + + +def url_concat( + url: str, + args: Union[ + None, Dict[str, str], List[Tuple[str, str]], Tuple[Tuple[str, str], ...] + ], +) -> str: + """Concatenate url and arguments regardless of whether + url has existing query parameters. + + ``args`` may be either a dictionary or a list of key-value pairs + (the latter allows for multiple values with the same key. + + >>> url_concat("http://example.com/foo", dict(c="d")) + 'http://example.com/foo?c=d' + >>> url_concat("http://example.com/foo?a=b", dict(c="d")) + 'http://example.com/foo?a=b&c=d' + >>> url_concat("http://example.com/foo?a=b", [("c", "d"), ("c", "d2")]) + 'http://example.com/foo?a=b&c=d&c=d2' + """ + if args is None: + return url + parsed_url = urlparse(url) + if isinstance(args, dict): + parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True) + parsed_query.extend(args.items()) + elif isinstance(args, list) or isinstance(args, tuple): + parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True) + parsed_query.extend(args) + else: + err = "'args' parameter should be dict, list or tuple. Not {0}".format( + type(args) + ) + raise TypeError(err) + final_query = urlencode(parsed_query) + url = urlunparse( + ( + parsed_url[0], + parsed_url[1], + parsed_url[2], + parsed_url[3], + final_query, + parsed_url[5], + ) + ) + return url + + +class HTTPFile(ObjectDict): + """Represents a file uploaded via a form. + + For backwards compatibility, its instance attributes are also + accessible as dictionary keys. + + * ``filename`` + * ``body`` + * ``content_type`` + """ + + pass + + +def _parse_request_range( + range_header: str +) -> Optional[Tuple[Optional[int], Optional[int]]]: + """Parses a Range header. + + Returns either ``None`` or tuple ``(start, end)``. + Note that while the HTTP headers use inclusive byte positions, + this method returns indexes suitable for use in slices. + + >>> start, end = _parse_request_range("bytes=1-2") + >>> start, end + (1, 3) + >>> [0, 1, 2, 3, 4][start:end] + [1, 2] + >>> _parse_request_range("bytes=6-") + (6, None) + >>> _parse_request_range("bytes=-6") + (-6, None) + >>> _parse_request_range("bytes=-0") + (None, 0) + >>> _parse_request_range("bytes=") + (None, None) + >>> _parse_request_range("foo=42") + >>> _parse_request_range("bytes=1-2,6-10") + + Note: only supports one range (ex, ``bytes=1-2,6-10`` is not allowed). + + See [0] for the details of the range header. + + [0]: http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#byte.ranges + """ + unit, _, value = range_header.partition("=") + unit, value = unit.strip(), value.strip() + if unit != "bytes": + return None + start_b, _, end_b = value.partition("-") + try: + start = _int_or_none(start_b) + end = _int_or_none(end_b) + except ValueError: + return None + if end is not None: + if start is None: + if end != 0: + start = -end + end = None + else: + end += 1 + return (start, end) + + +def _get_content_range(start: Optional[int], end: Optional[int], total: int) -> str: + """Returns a suitable Content-Range header: + + >>> print(_get_content_range(None, 1, 4)) + bytes 0-0/4 + >>> print(_get_content_range(1, 3, 4)) + bytes 1-2/4 + >>> print(_get_content_range(None, None, 4)) + bytes 0-3/4 + """ + start = start or 0 + end = (end or total) - 1 + return "bytes %s-%s/%s" % (start, end, total) + + +def _int_or_none(val: str) -> Optional[int]: + val = val.strip() + if val == "": + return None + return int(val) + + +def parse_body_arguments( + content_type: str, + body: bytes, + arguments: Dict[str, List[bytes]], + files: Dict[str, List[HTTPFile]], + headers: HTTPHeaders = None, +) -> None: + """Parses a form request body. + + Supports ``application/x-www-form-urlencoded`` and + ``multipart/form-data``. The ``content_type`` parameter should be + a string and ``body`` should be a byte string. The ``arguments`` + and ``files`` parameters are dictionaries that will be updated + with the parsed contents. + """ + if content_type.startswith("application/x-www-form-urlencoded"): + if headers and "Content-Encoding" in headers: + gen_log.warning( + "Unsupported Content-Encoding: %s", headers["Content-Encoding"] + ) + return + try: + uri_arguments = parse_qs_bytes(native_str(body), keep_blank_values=True) + except Exception as e: + gen_log.warning("Invalid x-www-form-urlencoded body: %s", e) + uri_arguments = {} + for name, values in uri_arguments.items(): + if values: + arguments.setdefault(name, []).extend(values) + elif content_type.startswith("multipart/form-data"): + if headers and "Content-Encoding" in headers: + gen_log.warning( + "Unsupported Content-Encoding: %s", headers["Content-Encoding"] + ) + return + try: + fields = content_type.split(";") + for field in fields: + k, sep, v = field.strip().partition("=") + if k == "boundary" and v: + parse_multipart_form_data(utf8(v), body, arguments, files) + break + else: + raise ValueError("multipart boundary not found") + except Exception as e: + gen_log.warning("Invalid multipart/form-data: %s", e) + + +def parse_multipart_form_data( + boundary: bytes, + data: bytes, + arguments: Dict[str, List[bytes]], + files: Dict[str, List[HTTPFile]], +) -> None: + """Parses a ``multipart/form-data`` body. + + The ``boundary`` and ``data`` parameters are both byte strings. + The dictionaries given in the arguments and files parameters + will be updated with the contents of the body. + + .. versionchanged:: 5.1 + + Now recognizes non-ASCII filenames in RFC 2231/5987 + (``filename*=``) format. + """ + # The standard allows for the boundary to be quoted in the header, + # although it's rare (it happens at least for google app engine + # xmpp). I think we're also supposed to handle backslash-escapes + # here but I'll save that until we see a client that uses them + # in the wild. + if boundary.startswith(b'"') and boundary.endswith(b'"'): + boundary = boundary[1:-1] + final_boundary_index = data.rfind(b"--" + boundary + b"--") + if final_boundary_index == -1: + gen_log.warning("Invalid multipart/form-data: no final boundary") + return + parts = data[:final_boundary_index].split(b"--" + boundary + b"\r\n") + for part in parts: + if not part: + continue + eoh = part.find(b"\r\n\r\n") + if eoh == -1: + gen_log.warning("multipart/form-data missing headers") + continue + headers = HTTPHeaders.parse(part[:eoh].decode("utf-8")) + disp_header = headers.get("Content-Disposition", "") + disposition, disp_params = _parse_header(disp_header) + if disposition != "form-data" or not part.endswith(b"\r\n"): + gen_log.warning("Invalid multipart/form-data") + continue + value = part[eoh + 4 : -2] + if not disp_params.get("name"): + gen_log.warning("multipart/form-data value missing name") + continue + name = disp_params["name"] + if disp_params.get("filename"): + ctype = headers.get("Content-Type", "application/unknown") + files.setdefault(name, []).append( + HTTPFile( + filename=disp_params["filename"], body=value, content_type=ctype + ) + ) + else: + arguments.setdefault(name, []).append(value) + + +def format_timestamp( + ts: Union[int, float, tuple, time.struct_time, datetime.datetime] +) -> str: + """Formats a timestamp in the format used by HTTP. + + The argument may be a numeric timestamp as returned by `time.time`, + a time tuple as returned by `time.gmtime`, or a `datetime.datetime` + object. + + >>> format_timestamp(1359312200) + 'Sun, 27 Jan 2013 18:43:20 GMT' + """ + if isinstance(ts, (int, float)): + time_num = ts + elif isinstance(ts, (tuple, time.struct_time)): + time_num = calendar.timegm(ts) + elif isinstance(ts, datetime.datetime): + time_num = calendar.timegm(ts.utctimetuple()) + else: + raise TypeError("unknown timestamp type: %r" % ts) + return email.utils.formatdate(time_num, usegmt=True) + + +RequestStartLine = collections.namedtuple( + "RequestStartLine", ["method", "path", "version"] +) + + +def parse_request_start_line(line: str) -> RequestStartLine: + """Returns a (method, path, version) tuple for an HTTP 1.x request line. + + The response is a `collections.namedtuple`. + + >>> parse_request_start_line("GET /foo HTTP/1.1") + RequestStartLine(method='GET', path='/foo', version='HTTP/1.1') + """ + try: + method, path, version = line.split(" ") + except ValueError: + # https://tools.ietf.org/html/rfc7230#section-3.1.1 + # invalid request-line SHOULD respond with a 400 (Bad Request) + raise HTTPInputError("Malformed HTTP request line") + if not re.match(r"^HTTP/1\.[0-9]$", version): + raise HTTPInputError( + "Malformed HTTP version in HTTP Request-Line: %r" % version + ) + return RequestStartLine(method, path, version) + + +ResponseStartLine = collections.namedtuple( + "ResponseStartLine", ["version", "code", "reason"] +) + + +def parse_response_start_line(line: str) -> ResponseStartLine: + """Returns a (version, code, reason) tuple for an HTTP 1.x response line. + + The response is a `collections.namedtuple`. + + >>> parse_response_start_line("HTTP/1.1 200 OK") + ResponseStartLine(version='HTTP/1.1', code=200, reason='OK') + """ + line = native_str(line) + match = re.match("(HTTP/1.[0-9]) ([0-9]+) ([^\r]*)", line) + if not match: + raise HTTPInputError("Error parsing response start line") + return ResponseStartLine(match.group(1), int(match.group(2)), match.group(3)) + + +# _parseparam and _parse_header are copied and modified from python2.7's cgi.py +# The original 2.7 version of this code did not correctly support some +# combinations of semicolons and double quotes. +# It has also been modified to support valueless parameters as seen in +# websocket extension negotiations, and to support non-ascii values in +# RFC 2231/5987 format. + + +def _parseparam(s: str) -> Generator[str, None, None]: + while s[:1] == ";": + s = s[1:] + end = s.find(";") + while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2: + end = s.find(";", end + 1) + if end < 0: + end = len(s) + f = s[:end] + yield f.strip() + s = s[end:] + + +def _parse_header(line: str) -> Tuple[str, Dict[str, str]]: + r"""Parse a Content-type like header. + + Return the main content-type and a dictionary of options. + + >>> d = "form-data; foo=\"b\\\\a\\\"r\"; file*=utf-8''T%C3%A4st" + >>> ct, d = _parse_header(d) + >>> ct + 'form-data' + >>> d['file'] == r'T\u00e4st'.encode('ascii').decode('unicode_escape') + True + >>> d['foo'] + 'b\\a"r' + """ + parts = _parseparam(";" + line) + key = next(parts) + # decode_params treats first argument special, but we already stripped key + params = [("Dummy", "value")] + for p in parts: + i = p.find("=") + if i >= 0: + name = p[:i].strip().lower() + value = p[i + 1 :].strip() + params.append((name, native_str(value))) + decoded_params = email.utils.decode_params(params) + decoded_params.pop(0) # get rid of the dummy again + pdict = {} + for name, decoded_value in decoded_params: + value = email.utils.collapse_rfc2231_value(decoded_value) + if len(value) >= 2 and value[0] == '"' and value[-1] == '"': + value = value[1:-1] + pdict[name] = value + return key, pdict + + +def _encode_header(key: str, pdict: Dict[str, str]) -> str: + """Inverse of _parse_header. + + >>> _encode_header('permessage-deflate', + ... {'client_max_window_bits': 15, 'client_no_context_takeover': None}) + 'permessage-deflate; client_max_window_bits=15; client_no_context_takeover' + """ + if not pdict: + return key + out = [key] + # Sort the parameters just to make it easy to test. + for k, v in sorted(pdict.items()): + if v is None: + out.append(k) + else: + # TODO: quote if necessary. + out.append("%s=%s" % (k, v)) + return "; ".join(out) + + +def encode_username_password( + username: Union[str, bytes], password: Union[str, bytes] +) -> bytes: + """Encodes a username/password pair in the format used by HTTP auth. + + The return value is a byte string in the form ``username:password``. + + .. versionadded:: 5.1 + """ + if isinstance(username, unicode_type): + username = unicodedata.normalize("NFC", username) + if isinstance(password, unicode_type): + password = unicodedata.normalize("NFC", password) + return utf8(username) + b":" + utf8(password) + + +def doctests(): + # type: () -> unittest.TestSuite + import doctest + + return doctest.DocTestSuite() + + +def split_host_and_port(netloc: str) -> Tuple[str, Optional[int]]: + """Returns ``(host, port)`` tuple from ``netloc``. + + Returned ``port`` will be ``None`` if not present. + + .. versionadded:: 4.1 + """ + match = re.match(r"^(.+):(\d+)$", netloc) + if match: + host = match.group(1) + port = int(match.group(2)) # type: Optional[int] + else: + host = netloc + port = None + return (host, port) + + +def qs_to_qsl(qs: Dict[str, List[AnyStr]]) -> Iterable[Tuple[str, AnyStr]]: + """Generator converting a result of ``parse_qs`` back to name-value pairs. + + .. versionadded:: 5.0 + """ + for k, vs in qs.items(): + for v in vs: + yield (k, v) + + +_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") +_QuotePatt = re.compile(r"[\\].") +_nulljoin = "".join + + +def _unquote_cookie(s: str) -> str: + """Handle double quotes and escaping in cookie values. + + This method is copied verbatim from the Python 3.5 standard + library (http.cookies._unquote) so we don't have to depend on + non-public interfaces. + """ + # If there aren't any doublequotes, + # then there can't be any special characters. See RFC 2109. + if s is None or len(s) < 2: + return s + if s[0] != '"' or s[-1] != '"': + return s + + # We have to assume that we must decode this string. + # Down to work. + + # Remove the "s + s = s[1:-1] + + # Check for special sequences. Examples: + # \012 --> \n + # \" --> " + # + i = 0 + n = len(s) + res = [] + while 0 <= i < n: + o_match = _OctalPatt.search(s, i) + q_match = _QuotePatt.search(s, i) + if not o_match and not q_match: # Neither matched + res.append(s[i:]) + break + # else: + j = k = -1 + if o_match: + j = o_match.start(0) + if q_match: + k = q_match.start(0) + if q_match and (not o_match or k < j): # QuotePatt matched + res.append(s[i:k]) + res.append(s[k + 1]) + i = k + 2 + else: # OctalPatt matched + res.append(s[i:j]) + res.append(chr(int(s[j + 1 : j + 4], 8))) + i = j + 4 + return _nulljoin(res) + + +def parse_cookie(cookie: str) -> Dict[str, str]: + """Parse a ``Cookie`` HTTP header into a dict of name/value pairs. + + This function attempts to mimic browser cookie parsing behavior; + it specifically does not follow any of the cookie-related RFCs + (because browsers don't either). + + The algorithm used is identical to that used by Django version 1.9.10. + + .. versionadded:: 4.4.2 + """ + cookiedict = {} + for chunk in cookie.split(str(";")): + if str("=") in chunk: + key, val = chunk.split(str("="), 1) + else: + # Assume an empty name per + # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 + key, val = str(""), chunk + key, val = key.strip(), val.strip() + if key or val: + # unquote using Python's algorithm. + cookiedict[key] = _unquote_cookie(val) + return cookiedict diff --git a/venv/lib/python3.7/site-packages/tornado/ioloop.py b/venv/lib/python3.7/site-packages/tornado/ioloop.py new file mode 100644 index 0000000..02794cb --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/ioloop.py @@ -0,0 +1,946 @@ +# +# Copyright 2009 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. + +"""An I/O event loop for non-blocking sockets. + +In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event +loop, with a slightly different interface for historical reasons. +Applications can use either the `.IOLoop` interface or the underlying +`asyncio` event loop directly (unless compatibility with older +versions of Tornado is desired, in which case `.IOLoop` must be used). + +Typical applications will use a single `IOLoop` object, accessed via +`IOLoop.current` class method. The `IOLoop.start` method (or +equivalently, `asyncio.AbstractEventLoop.run_forever`) should usually +be called at the end of the ``main()`` function. Atypical applications +may use more than one `IOLoop`, such as one `IOLoop` per thread, or +per `unittest` case. + +""" + +import asyncio +import concurrent.futures +import datetime +import functools +import logging +import numbers +import os +import sys +import time +import math +import random + +from tornado.concurrent import ( + Future, + is_future, + chain_future, + future_set_exc_info, + future_add_done_callback, +) +from tornado.log import app_log +from tornado.util import Configurable, TimeoutError, import_object + +import typing +from typing import Union, Any, Type, Optional, Callable, TypeVar, Tuple, Awaitable + +if typing.TYPE_CHECKING: + from typing import Dict, List # noqa: F401 + + from typing_extensions import Protocol +else: + Protocol = object + + +class _Selectable(Protocol): + def fileno(self) -> int: + pass + + def close(self) -> None: + pass + + +_T = TypeVar("_T") +_S = TypeVar("_S", bound=_Selectable) + + +class IOLoop(Configurable): + """An I/O event loop. + + As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event + loop. + + Example usage for a simple TCP server: + + .. testcode:: + + import errno + import functools + import socket + + import tornado.ioloop + from tornado.iostream import IOStream + + async def handle_connection(connection, address): + stream = IOStream(connection) + message = await stream.read_until_close() + print("message from client:", message.decode().strip()) + + def connection_ready(sock, fd, events): + while True: + try: + connection, address = sock.accept() + except socket.error as e: + if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN): + raise + return + connection.setblocking(0) + io_loop = tornado.ioloop.IOLoop.current() + io_loop.spawn_callback(handle_connection, connection, address) + + if __name__ == '__main__': + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.setblocking(0) + sock.bind(("", 8888)) + sock.listen(128) + + io_loop = tornado.ioloop.IOLoop.current() + callback = functools.partial(connection_ready, sock) + io_loop.add_handler(sock.fileno(), callback, io_loop.READ) + io_loop.start() + + .. testoutput:: + :hide: + + By default, a newly-constructed `IOLoop` becomes the thread's current + `IOLoop`, unless there already is a current `IOLoop`. This behavior + can be controlled with the ``make_current`` argument to the `IOLoop` + constructor: if ``make_current=True``, the new `IOLoop` will always + try to become current and it raises an error if there is already a + current instance. If ``make_current=False``, the new `IOLoop` will + not try to become current. + + In general, an `IOLoop` cannot survive a fork or be shared across + processes in any way. When multiple processes are being used, each + process should create its own `IOLoop`, which also implies that + any objects which depend on the `IOLoop` (such as + `.AsyncHTTPClient`) must also be created in the child processes. + As a guideline, anything that starts processes (including the + `tornado.process` and `multiprocessing` modules) should do so as + early as possible, ideally the first thing the application does + after loading its configuration in ``main()``. + + .. versionchanged:: 4.2 + Added the ``make_current`` keyword argument to the `IOLoop` + constructor. + + .. versionchanged:: 5.0 + + Uses the `asyncio` event loop by default. The + ``IOLoop.configure`` method cannot be used on Python 3 except + to redundantly specify the `asyncio` event loop. + + """ + + # These constants were originally based on constants from the epoll module. + NONE = 0 + READ = 0x001 + WRITE = 0x004 + ERROR = 0x018 + + # In Python 3, _ioloop_for_asyncio maps from asyncio loops to IOLoops. + _ioloop_for_asyncio = dict() # type: Dict[asyncio.AbstractEventLoop, IOLoop] + + @classmethod + def configure( + cls, impl: "Union[None, str, Type[Configurable]]", **kwargs: Any + ) -> None: + if asyncio is not None: + from tornado.platform.asyncio import BaseAsyncIOLoop + + if isinstance(impl, str): + impl = import_object(impl) + if isinstance(impl, type) and not issubclass(impl, BaseAsyncIOLoop): + raise RuntimeError( + "only AsyncIOLoop is allowed when asyncio is available" + ) + super(IOLoop, cls).configure(impl, **kwargs) + + @staticmethod + def instance() -> "IOLoop": + """Deprecated alias for `IOLoop.current()`. + + .. versionchanged:: 5.0 + + Previously, this method returned a global singleton + `IOLoop`, in contrast with the per-thread `IOLoop` returned + by `current()`. In nearly all cases the two were the same + (when they differed, it was generally used from non-Tornado + threads to communicate back to the main thread's `IOLoop`). + This distinction is not present in `asyncio`, so in order + to facilitate integration with that package `instance()` + was changed to be an alias to `current()`. Applications + using the cross-thread communications aspect of + `instance()` should instead set their own global variable + to point to the `IOLoop` they want to use. + + .. deprecated:: 5.0 + """ + return IOLoop.current() + + def install(self) -> None: + """Deprecated alias for `make_current()`. + + .. versionchanged:: 5.0 + + Previously, this method would set this `IOLoop` as the + global singleton used by `IOLoop.instance()`. Now that + `instance()` is an alias for `current()`, `install()` + is an alias for `make_current()`. + + .. deprecated:: 5.0 + """ + self.make_current() + + @staticmethod + def clear_instance() -> None: + """Deprecated alias for `clear_current()`. + + .. versionchanged:: 5.0 + + Previously, this method would clear the `IOLoop` used as + the global singleton by `IOLoop.instance()`. Now that + `instance()` is an alias for `current()`, + `clear_instance()` is an alias for `clear_current()`. + + .. deprecated:: 5.0 + + """ + IOLoop.clear_current() + + @typing.overload + @staticmethod + def current() -> "IOLoop": + pass + + @typing.overload # noqa: F811 + @staticmethod + def current(instance: bool = True) -> Optional["IOLoop"]: + pass + + @staticmethod # noqa: F811 + def current(instance: bool = True) -> Optional["IOLoop"]: + """Returns the current thread's `IOLoop`. + + If an `IOLoop` is currently running or has been marked as + current by `make_current`, returns that instance. If there is + no current `IOLoop` and ``instance`` is true, creates one. + + .. versionchanged:: 4.1 + Added ``instance`` argument to control the fallback to + `IOLoop.instance()`. + .. versionchanged:: 5.0 + On Python 3, control of the current `IOLoop` is delegated + to `asyncio`, with this and other methods as pass-through accessors. + The ``instance`` argument now controls whether an `IOLoop` + is created automatically when there is none, instead of + whether we fall back to `IOLoop.instance()` (which is now + an alias for this method). ``instance=False`` is deprecated, + since even if we do not create an `IOLoop`, this method + may initialize the asyncio loop. + """ + try: + loop = asyncio.get_event_loop() + except (RuntimeError, AssertionError): + if not instance: + return None + raise + try: + return IOLoop._ioloop_for_asyncio[loop] + except KeyError: + if instance: + from tornado.platform.asyncio import AsyncIOMainLoop + + current = AsyncIOMainLoop(make_current=True) # type: Optional[IOLoop] + else: + current = None + return current + + def make_current(self) -> None: + """Makes this the `IOLoop` for the current thread. + + An `IOLoop` automatically becomes current for its thread + when it is started, but it is sometimes useful to call + `make_current` explicitly before starting the `IOLoop`, + so that code run at startup time can find the right + instance. + + .. versionchanged:: 4.1 + An `IOLoop` created while there is no current `IOLoop` + will automatically become current. + + .. versionchanged:: 5.0 + This method also sets the current `asyncio` event loop. + """ + # The asyncio event loops override this method. + raise NotImplementedError() + + @staticmethod + def clear_current() -> None: + """Clears the `IOLoop` for the current thread. + + Intended primarily for use by test frameworks in between tests. + + .. versionchanged:: 5.0 + This method also clears the current `asyncio` event loop. + """ + old = IOLoop.current(instance=False) + if old is not None: + old._clear_current_hook() + if asyncio is None: + IOLoop._current.instance = None + + def _clear_current_hook(self) -> None: + """Instance method called when an IOLoop ceases to be current. + + May be overridden by subclasses as a counterpart to make_current. + """ + pass + + @classmethod + def configurable_base(cls) -> Type[Configurable]: + return IOLoop + + @classmethod + def configurable_default(cls) -> Type[Configurable]: + from tornado.platform.asyncio import AsyncIOLoop + + return AsyncIOLoop + + def initialize(self, make_current: bool = None) -> None: + if make_current is None: + if IOLoop.current(instance=False) is None: + self.make_current() + elif make_current: + current = IOLoop.current(instance=False) + # AsyncIO loops can already be current by this point. + if current is not None and current is not self: + raise RuntimeError("current IOLoop already exists") + self.make_current() + + def close(self, all_fds: bool = False) -> None: + """Closes the `IOLoop`, freeing any resources used. + + If ``all_fds`` is true, all file descriptors registered on the + IOLoop will be closed (not just the ones created by the + `IOLoop` itself). + + Many applications will only use a single `IOLoop` that runs for the + entire lifetime of the process. In that case closing the `IOLoop` + is not necessary since everything will be cleaned up when the + process exits. `IOLoop.close` is provided mainly for scenarios + such as unit tests, which create and destroy a large number of + ``IOLoops``. + + An `IOLoop` must be completely stopped before it can be closed. This + means that `IOLoop.stop()` must be called *and* `IOLoop.start()` must + be allowed to return before attempting to call `IOLoop.close()`. + Therefore the call to `close` will usually appear just after + the call to `start` rather than near the call to `stop`. + + .. versionchanged:: 3.1 + If the `IOLoop` implementation supports non-integer objects + for "file descriptors", those objects will have their + ``close`` method when ``all_fds`` is true. + """ + raise NotImplementedError() + + @typing.overload + def add_handler( + self, fd: int, handler: Callable[[int, int], None], events: int + ) -> None: + pass + + @typing.overload # noqa: F811 + def add_handler( + self, fd: _S, handler: Callable[[_S, int], None], events: int + ) -> None: + pass + + def add_handler( # noqa: F811 + self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int + ) -> None: + """Registers the given handler to receive the given events for ``fd``. + + The ``fd`` argument may either be an integer file descriptor or + a file-like object with a ``fileno()`` and ``close()`` method. + + The ``events`` argument is a bitwise or of the constants + ``IOLoop.READ``, ``IOLoop.WRITE``, and ``IOLoop.ERROR``. + + When an event occurs, ``handler(fd, events)`` will be run. + + .. versionchanged:: 4.0 + Added the ability to pass file-like objects in addition to + raw file descriptors. + """ + raise NotImplementedError() + + def update_handler(self, fd: Union[int, _Selectable], events: int) -> None: + """Changes the events we listen for ``fd``. + + .. versionchanged:: 4.0 + Added the ability to pass file-like objects in addition to + raw file descriptors. + """ + raise NotImplementedError() + + def remove_handler(self, fd: Union[int, _Selectable]) -> None: + """Stop listening for events on ``fd``. + + .. versionchanged:: 4.0 + Added the ability to pass file-like objects in addition to + raw file descriptors. + """ + raise NotImplementedError() + + def start(self) -> None: + """Starts the I/O loop. + + The loop will run until one of the callbacks calls `stop()`, which + will make the loop stop after the current event iteration completes. + """ + raise NotImplementedError() + + def _setup_logging(self) -> None: + """The IOLoop catches and logs exceptions, so it's + important that log output be visible. However, python's + default behavior for non-root loggers (prior to python + 3.2) is to print an unhelpful "no handlers could be + found" message rather than the actual log entry, so we + must explicitly configure logging if we've made it this + far without anything. + + This method should be called from start() in subclasses. + """ + if not any( + [ + logging.getLogger().handlers, + logging.getLogger("tornado").handlers, + logging.getLogger("tornado.application").handlers, + ] + ): + logging.basicConfig() + + def stop(self) -> None: + """Stop the I/O loop. + + If the event loop is not currently running, the next call to `start()` + will return immediately. + + Note that even after `stop` has been called, the `IOLoop` is not + completely stopped until `IOLoop.start` has also returned. + Some work that was scheduled before the call to `stop` may still + be run before the `IOLoop` shuts down. + """ + raise NotImplementedError() + + def run_sync(self, func: Callable, timeout: float = None) -> Any: + """Starts the `IOLoop`, runs the given function, and stops the loop. + + The function must return either an awaitable object or + ``None``. If the function returns an awaitable object, the + `IOLoop` will run until the awaitable is resolved (and + `run_sync()` will return the awaitable's result). If it raises + an exception, the `IOLoop` will stop and the exception will be + re-raised to the caller. + + The keyword-only argument ``timeout`` may be used to set + a maximum duration for the function. If the timeout expires, + a `tornado.util.TimeoutError` is raised. + + This method is useful to allow asynchronous calls in a + ``main()`` function:: + + async def main(): + # do stuff... + + if __name__ == '__main__': + IOLoop.current().run_sync(main) + + .. versionchanged:: 4.3 + Returning a non-``None``, non-awaitable value is now an error. + + .. versionchanged:: 5.0 + If a timeout occurs, the ``func`` coroutine will be cancelled. + + """ + future_cell = [None] # type: List[Optional[Future]] + + def run() -> None: + try: + result = func() + if result is not None: + from tornado.gen import convert_yielded + + result = convert_yielded(result) + except Exception: + fut = Future() # type: Future[Any] + future_cell[0] = fut + future_set_exc_info(fut, sys.exc_info()) + else: + if is_future(result): + future_cell[0] = result + else: + fut = Future() + future_cell[0] = fut + fut.set_result(result) + assert future_cell[0] is not None + self.add_future(future_cell[0], lambda future: self.stop()) + + self.add_callback(run) + if timeout is not None: + + def timeout_callback() -> None: + # If we can cancel the future, do so and wait on it. If not, + # Just stop the loop and return with the task still pending. + # (If we neither cancel nor wait for the task, a warning + # will be logged). + assert future_cell[0] is not None + if not future_cell[0].cancel(): + self.stop() + + timeout_handle = self.add_timeout(self.time() + timeout, timeout_callback) + self.start() + if timeout is not None: + self.remove_timeout(timeout_handle) + assert future_cell[0] is not None + if future_cell[0].cancelled() or not future_cell[0].done(): + raise TimeoutError("Operation timed out after %s seconds" % timeout) + return future_cell[0].result() + + def time(self) -> float: + """Returns the current time according to the `IOLoop`'s clock. + + The return value is a floating-point number relative to an + unspecified time in the past. + + Historically, the IOLoop could be customized to use e.g. + `time.monotonic` instead of `time.time`, but this is not + currently supported and so this method is equivalent to + `time.time`. + + """ + return time.time() + + def add_timeout( + self, + deadline: Union[float, datetime.timedelta], + callback: Callable[..., None], + *args: Any, + **kwargs: Any + ) -> object: + """Runs the ``callback`` at the time ``deadline`` from the I/O loop. + + Returns an opaque handle that may be passed to + `remove_timeout` to cancel. + + ``deadline`` may be a number denoting a time (on the same + scale as `IOLoop.time`, normally `time.time`), or a + `datetime.timedelta` object for a deadline relative to the + current time. Since Tornado 4.0, `call_later` is a more + convenient alternative for the relative case since it does not + require a timedelta object. + + Note that it is not safe to call `add_timeout` from other threads. + Instead, you must use `add_callback` to transfer control to the + `IOLoop`'s thread, and then call `add_timeout` from there. + + Subclasses of IOLoop must implement either `add_timeout` or + `call_at`; the default implementations of each will call + the other. `call_at` is usually easier to implement, but + subclasses that wish to maintain compatibility with Tornado + versions prior to 4.0 must use `add_timeout` instead. + + .. versionchanged:: 4.0 + Now passes through ``*args`` and ``**kwargs`` to the callback. + """ + if isinstance(deadline, numbers.Real): + return self.call_at(deadline, callback, *args, **kwargs) + elif isinstance(deadline, datetime.timedelta): + return self.call_at( + self.time() + deadline.total_seconds(), callback, *args, **kwargs + ) + else: + raise TypeError("Unsupported deadline %r" % deadline) + + def call_later( + self, delay: float, callback: Callable[..., None], *args: Any, **kwargs: Any + ) -> object: + """Runs the ``callback`` after ``delay`` seconds have passed. + + Returns an opaque handle that may be passed to `remove_timeout` + to cancel. Note that unlike the `asyncio` method of the same + name, the returned object does not have a ``cancel()`` method. + + See `add_timeout` for comments on thread-safety and subclassing. + + .. versionadded:: 4.0 + """ + return self.call_at(self.time() + delay, callback, *args, **kwargs) + + def call_at( + self, when: float, callback: Callable[..., None], *args: Any, **kwargs: Any + ) -> object: + """Runs the ``callback`` at the absolute time designated by ``when``. + + ``when`` must be a number using the same reference point as + `IOLoop.time`. + + Returns an opaque handle that may be passed to `remove_timeout` + to cancel. Note that unlike the `asyncio` method of the same + name, the returned object does not have a ``cancel()`` method. + + See `add_timeout` for comments on thread-safety and subclassing. + + .. versionadded:: 4.0 + """ + return self.add_timeout(when, callback, *args, **kwargs) + + def remove_timeout(self, timeout: object) -> None: + """Cancels a pending timeout. + + The argument is a handle as returned by `add_timeout`. It is + safe to call `remove_timeout` even if the callback has already + been run. + """ + raise NotImplementedError() + + def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: + """Calls the given callback on the next I/O loop iteration. + + It is safe to call this method from any thread at any time, + except from a signal handler. Note that this is the **only** + method in `IOLoop` that makes this thread-safety guarantee; all + other interaction with the `IOLoop` must be done from that + `IOLoop`'s thread. `add_callback()` may be used to transfer + control from other threads to the `IOLoop`'s thread. + + To add a callback from a signal handler, see + `add_callback_from_signal`. + """ + raise NotImplementedError() + + def add_callback_from_signal( + self, callback: Callable, *args: Any, **kwargs: Any + ) -> None: + """Calls the given callback on the next I/O loop iteration. + + Safe for use from a Python signal handler; should not be used + otherwise. + """ + raise NotImplementedError() + + def spawn_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: + """Calls the given callback on the next IOLoop iteration. + + As of Tornado 6.0, this method is equivalent to `add_callback`. + + .. versionadded:: 4.0 + """ + self.add_callback(callback, *args, **kwargs) + + def add_future( + self, + future: "Union[Future[_T], concurrent.futures.Future[_T]]", + callback: Callable[["Future[_T]"], None], + ) -> None: + """Schedules a callback on the ``IOLoop`` when the given + `.Future` is finished. + + The callback is invoked with one argument, the + `.Future`. + + This method only accepts `.Future` objects and not other + awaitables (unlike most of Tornado where the two are + interchangeable). + """ + if isinstance(future, Future): + # Note that we specifically do not want the inline behavior of + # tornado.concurrent.future_add_done_callback. We always want + # this callback scheduled on the next IOLoop iteration (which + # asyncio.Future always does). + # + # Wrap the callback in self._run_callback so we control + # the error logging (i.e. it goes to tornado.log.app_log + # instead of asyncio's log). + future.add_done_callback( + lambda f: self._run_callback(functools.partial(callback, future)) + ) + else: + assert is_future(future) + # For concurrent futures, we use self.add_callback, so + # it's fine if future_add_done_callback inlines that call. + future_add_done_callback( + future, lambda f: self.add_callback(callback, future) + ) + + def run_in_executor( + self, + executor: Optional[concurrent.futures.Executor], + func: Callable[..., _T], + *args: Any + ) -> Awaitable[_T]: + """Runs a function in a ``concurrent.futures.Executor``. If + ``executor`` is ``None``, the IO loop's default executor will be used. + + Use `functools.partial` to pass keyword arguments to ``func``. + + .. versionadded:: 5.0 + """ + if executor is None: + if not hasattr(self, "_executor"): + from tornado.process import cpu_count + + self._executor = concurrent.futures.ThreadPoolExecutor( + max_workers=(cpu_count() * 5) + ) # type: concurrent.futures.Executor + executor = self._executor + c_future = executor.submit(func, *args) + # Concurrent Futures are not usable with await. Wrap this in a + # Tornado Future instead, using self.add_future for thread-safety. + t_future = Future() # type: Future[_T] + self.add_future(c_future, lambda f: chain_future(f, t_future)) + return t_future + + def set_default_executor(self, executor: concurrent.futures.Executor) -> None: + """Sets the default executor to use with :meth:`run_in_executor`. + + .. versionadded:: 5.0 + """ + self._executor = executor + + def _run_callback(self, callback: Callable[[], Any]) -> None: + """Runs a callback with error handling. + + .. versionchanged:: 6.0 + + CancelledErrors are no longer logged. + """ + try: + ret = callback() + if ret is not None: + from tornado import gen + + # Functions that return Futures typically swallow all + # exceptions and store them in the Future. If a Future + # makes it out to the IOLoop, ensure its exception (if any) + # gets logged too. + try: + ret = gen.convert_yielded(ret) + except gen.BadYieldError: + # It's not unusual for add_callback to be used with + # methods returning a non-None and non-yieldable + # result, which should just be ignored. + pass + else: + self.add_future(ret, self._discard_future_result) + except asyncio.CancelledError: + pass + except Exception: + app_log.error("Exception in callback %r", callback, exc_info=True) + + def _discard_future_result(self, future: Future) -> None: + """Avoid unhandled-exception warnings from spawned coroutines.""" + future.result() + + def split_fd( + self, fd: Union[int, _Selectable] + ) -> Tuple[int, Union[int, _Selectable]]: + # """Returns an (fd, obj) pair from an ``fd`` parameter. + + # We accept both raw file descriptors and file-like objects as + # input to `add_handler` and related methods. When a file-like + # object is passed, we must retain the object itself so we can + # close it correctly when the `IOLoop` shuts down, but the + # poller interfaces favor file descriptors (they will accept + # file-like objects and call ``fileno()`` for you, but they + # always return the descriptor itself). + + # This method is provided for use by `IOLoop` subclasses and should + # not generally be used by application code. + + # .. versionadded:: 4.0 + # """ + if isinstance(fd, int): + return fd, fd + return fd.fileno(), fd + + def close_fd(self, fd: Union[int, _Selectable]) -> None: + # """Utility method to close an ``fd``. + + # If ``fd`` is a file-like object, we close it directly; otherwise + # we use `os.close`. + + # This method is provided for use by `IOLoop` subclasses (in + # implementations of ``IOLoop.close(all_fds=True)`` and should + # not generally be used by application code. + + # .. versionadded:: 4.0 + # """ + try: + if isinstance(fd, int): + os.close(fd) + else: + fd.close() + except OSError: + pass + + +class _Timeout(object): + """An IOLoop timeout, a UNIX timestamp and a callback""" + + # Reduce memory overhead when there are lots of pending callbacks + __slots__ = ["deadline", "callback", "tdeadline"] + + def __init__( + self, deadline: float, callback: Callable[[], None], io_loop: IOLoop + ) -> None: + if not isinstance(deadline, numbers.Real): + raise TypeError("Unsupported deadline %r" % deadline) + self.deadline = deadline + self.callback = callback + self.tdeadline = ( + deadline, + next(io_loop._timeout_counter), + ) # type: Tuple[float, int] + + # Comparison methods to sort by deadline, with object id as a tiebreaker + # to guarantee a consistent ordering. The heapq module uses __le__ + # in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons + # use __lt__). + def __lt__(self, other: "_Timeout") -> bool: + return self.tdeadline < other.tdeadline + + def __le__(self, other: "_Timeout") -> bool: + return self.tdeadline <= other.tdeadline + + +class PeriodicCallback(object): + """Schedules the given callback to be called periodically. + + The callback is called every ``callback_time`` milliseconds. + Note that the timeout is given in milliseconds, while most other + time-related functions in Tornado use seconds. + + If ``jitter`` is specified, each callback time will be randomly selected + within a window of ``jitter * callback_time`` milliseconds. + Jitter can be used to reduce alignment of events with similar periods. + A jitter of 0.1 means allowing a 10% variation in callback time. + The window is centered on ``callback_time`` so the total number of calls + within a given interval should not be significantly affected by adding + jitter. + + If the callback runs for longer than ``callback_time`` milliseconds, + subsequent invocations will be skipped to get back on schedule. + + `start` must be called after the `PeriodicCallback` is created. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + + .. versionchanged:: 5.1 + The ``jitter`` argument is added. + """ + + def __init__( + self, callback: Callable[[], None], callback_time: float, jitter: float = 0 + ) -> None: + self.callback = callback + if callback_time <= 0: + raise ValueError("Periodic callback must have a positive callback_time") + self.callback_time = callback_time + self.jitter = jitter + self._running = False + self._timeout = None # type: object + + def start(self) -> None: + """Starts the timer.""" + # Looking up the IOLoop here allows to first instantiate the + # PeriodicCallback in another thread, then start it using + # IOLoop.add_callback(). + self.io_loop = IOLoop.current() + self._running = True + self._next_timeout = self.io_loop.time() + self._schedule_next() + + def stop(self) -> None: + """Stops the timer.""" + self._running = False + if self._timeout is not None: + self.io_loop.remove_timeout(self._timeout) + self._timeout = None + + def is_running(self) -> bool: + """Returns ``True`` if this `.PeriodicCallback` has been started. + + .. versionadded:: 4.1 + """ + return self._running + + def _run(self) -> None: + if not self._running: + return + try: + return self.callback() + except Exception: + app_log.error("Exception in callback %r", self.callback, exc_info=True) + finally: + self._schedule_next() + + def _schedule_next(self) -> None: + if self._running: + self._update_next(self.io_loop.time()) + self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run) + + def _update_next(self, current_time: float) -> None: + callback_time_sec = self.callback_time / 1000.0 + if self.jitter: + # apply jitter fraction + callback_time_sec *= 1 + (self.jitter * (random.random() - 0.5)) + if self._next_timeout <= current_time: + # The period should be measured from the start of one call + # to the start of the next. If one call takes too long, + # skip cycles to get back to a multiple of the original + # schedule. + self._next_timeout += ( + math.floor((current_time - self._next_timeout) / callback_time_sec) + 1 + ) * callback_time_sec + else: + # If the clock moved backwards, ensure we advance the next + # timeout instead of recomputing the same value again. + # This may result in long gaps between callbacks if the + # clock jumps backwards by a lot, but the far more common + # scenario is a small NTP adjustment that should just be + # ignored. + # + # Note that on some systems if time.time() runs slower + # than time.monotonic() (most common on windows), we + # effectively experience a small backwards time jump on + # every iteration because PeriodicCallback uses + # time.time() while asyncio schedules callbacks using + # time.monotonic(). + # https://github.com/tornadoweb/tornado/issues/2333 + self._next_timeout += callback_time_sec diff --git a/venv/lib/python3.7/site-packages/tornado/iostream.py b/venv/lib/python3.7/site-packages/tornado/iostream.py new file mode 100644 index 0000000..ee30592 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/iostream.py @@ -0,0 +1,1655 @@ +# +# Copyright 2009 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. + +"""Utility classes to write to and read from non-blocking files and sockets. + +Contents: + +* `BaseIOStream`: Generic interface for reading and writing. +* `IOStream`: Implementation of BaseIOStream using non-blocking sockets. +* `SSLIOStream`: SSL-aware version of IOStream. +* `PipeIOStream`: Pipe-based IOStream implementation. +""" + +import asyncio +import collections +import errno +import io +import numbers +import os +import socket +import ssl +import sys +import re + +from tornado.concurrent import Future, future_set_result_unless_cancelled +from tornado import ioloop +from tornado.log import gen_log +from tornado.netutil import ssl_wrap_socket, _client_ssl_defaults, _server_ssl_defaults +from tornado.util import errno_from_exception + +import typing +from typing import ( + Union, + Optional, + Awaitable, + Callable, + Pattern, + Any, + Dict, + TypeVar, + Tuple, +) +from types import TracebackType + +if typing.TYPE_CHECKING: + from typing import Deque, List, Type # noqa: F401 + +_IOStreamType = TypeVar("_IOStreamType", bound="IOStream") + +try: + from tornado.platform.posix import _set_nonblocking +except ImportError: + _set_nonblocking = None # type: ignore + +# These errnos indicate that a non-blocking operation must be retried +# at a later time. On most platforms they're the same value, but on +# some they differ. +_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN) + +if hasattr(errno, "WSAEWOULDBLOCK"): + _ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,) # type: ignore + +# These errnos indicate that a connection has been abruptly terminated. +# They should be caught and handled less noisily than other errors. +_ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE, errno.ETIMEDOUT) + +if hasattr(errno, "WSAECONNRESET"): + _ERRNO_CONNRESET += ( # type: ignore + errno.WSAECONNRESET, # type: ignore + errno.WSAECONNABORTED, # type: ignore + errno.WSAETIMEDOUT, # type: ignore + ) + +if sys.platform == "darwin": + # OSX appears to have a race condition that causes send(2) to return + # EPROTOTYPE if called while a socket is being torn down: + # http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + # Since the socket is being closed anyway, treat this as an ECONNRESET + # instead of an unexpected error. + _ERRNO_CONNRESET += (errno.EPROTOTYPE,) # type: ignore + +# More non-portable errnos: +_ERRNO_INPROGRESS = (errno.EINPROGRESS,) + +if hasattr(errno, "WSAEINPROGRESS"): + _ERRNO_INPROGRESS += (errno.WSAEINPROGRESS,) # type: ignore + +_WINDOWS = sys.platform.startswith("win") + + +class StreamClosedError(IOError): + """Exception raised by `IOStream` methods when the stream is closed. + + Note that the close callback is scheduled to run *after* other + callbacks on the stream (to allow for buffered data to be processed), + so you may see this error before you see the close callback. + + The ``real_error`` attribute contains the underlying error that caused + the stream to close (if any). + + .. versionchanged:: 4.3 + Added the ``real_error`` attribute. + """ + + def __init__(self, real_error: BaseException = None) -> None: + super(StreamClosedError, self).__init__("Stream is closed") + self.real_error = real_error + + +class UnsatisfiableReadError(Exception): + """Exception raised when a read cannot be satisfied. + + Raised by ``read_until`` and ``read_until_regex`` with a ``max_bytes`` + argument. + """ + + pass + + +class StreamBufferFullError(Exception): + """Exception raised by `IOStream` methods when the buffer is full. + """ + + +class _StreamBuffer(object): + """ + A specialized buffer that tries to avoid copies when large pieces + of data are encountered. + """ + + def __init__(self) -> None: + # A sequence of (False, bytearray) and (True, memoryview) objects + self._buffers = ( + collections.deque() + ) # type: Deque[Tuple[bool, Union[bytearray, memoryview]]] + # Position in the first buffer + self._first_pos = 0 + self._size = 0 + + def __len__(self) -> int: + return self._size + + # Data above this size will be appended separately instead + # of extending an existing bytearray + _large_buf_threshold = 2048 + + def append(self, data: Union[bytes, bytearray, memoryview]) -> None: + """ + Append the given piece of data (should be a buffer-compatible object). + """ + size = len(data) + if size > self._large_buf_threshold: + if not isinstance(data, memoryview): + data = memoryview(data) + self._buffers.append((True, data)) + elif size > 0: + if self._buffers: + is_memview, b = self._buffers[-1] + new_buf = is_memview or len(b) >= self._large_buf_threshold + else: + new_buf = True + if new_buf: + self._buffers.append((False, bytearray(data))) + else: + b += data # type: ignore + + self._size += size + + def peek(self, size: int) -> memoryview: + """ + Get a view over at most ``size`` bytes (possibly fewer) at the + current buffer position. + """ + assert size > 0 + try: + is_memview, b = self._buffers[0] + except IndexError: + return memoryview(b"") + + pos = self._first_pos + if is_memview: + return typing.cast(memoryview, b[pos : pos + size]) + else: + return memoryview(b)[pos : pos + size] + + def advance(self, size: int) -> None: + """ + Advance the current buffer position by ``size`` bytes. + """ + assert 0 < size <= self._size + self._size -= size + pos = self._first_pos + + buffers = self._buffers + while buffers and size > 0: + is_large, b = buffers[0] + b_remain = len(b) - size - pos + if b_remain <= 0: + buffers.popleft() + size -= len(b) - pos + pos = 0 + elif is_large: + pos += size + size = 0 + else: + # Amortized O(1) shrink for Python 2 + pos += size + if len(b) <= 2 * pos: + del typing.cast(bytearray, b)[:pos] + pos = 0 + size = 0 + + assert size == 0 + self._first_pos = pos + + +class BaseIOStream(object): + """A utility class to write to and read from a non-blocking file or socket. + + We support a non-blocking ``write()`` and a family of ``read_*()`` + methods. When the operation completes, the ``Awaitable`` will resolve + with the data read (or ``None`` for ``write()``). All outstanding + ``Awaitables`` will resolve with a `StreamClosedError` when the + stream is closed; `.BaseIOStream.set_close_callback` can also be used + to be notified of a closed stream. + + When a stream is closed due to an error, the IOStream's ``error`` + attribute contains the exception object. + + Subclasses must implement `fileno`, `close_fd`, `write_to_fd`, + `read_from_fd`, and optionally `get_fd_error`. + + """ + + def __init__( + self, + max_buffer_size: int = None, + read_chunk_size: int = None, + max_write_buffer_size: int = None, + ) -> None: + """`BaseIOStream` constructor. + + :arg max_buffer_size: Maximum amount of incoming data to buffer; + defaults to 100MB. + :arg read_chunk_size: Amount of data to read at one time from the + underlying transport; defaults to 64KB. + :arg max_write_buffer_size: Amount of outgoing data to buffer; + defaults to unlimited. + + .. versionchanged:: 4.0 + Add the ``max_write_buffer_size`` parameter. Changed default + ``read_chunk_size`` to 64KB. + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been + removed. + """ + self.io_loop = ioloop.IOLoop.current() + self.max_buffer_size = max_buffer_size or 104857600 + # A chunk size that is too close to max_buffer_size can cause + # spurious failures. + self.read_chunk_size = min(read_chunk_size or 65536, self.max_buffer_size // 2) + self.max_write_buffer_size = max_write_buffer_size + self.error = None # type: Optional[BaseException] + self._read_buffer = bytearray() + self._read_buffer_pos = 0 + self._read_buffer_size = 0 + self._user_read_buffer = False + self._after_user_read_buffer = None # type: Optional[bytearray] + self._write_buffer = _StreamBuffer() + self._total_write_index = 0 + self._total_write_done_index = 0 + self._read_delimiter = None # type: Optional[bytes] + self._read_regex = None # type: Optional[Pattern] + self._read_max_bytes = None # type: Optional[int] + self._read_bytes = None # type: Optional[int] + self._read_partial = False + self._read_until_close = False + self._read_future = None # type: Optional[Future] + self._write_futures = ( + collections.deque() + ) # type: Deque[Tuple[int, Future[None]]] + self._close_callback = None # type: Optional[Callable[[], None]] + self._connect_future = None # type: Optional[Future[IOStream]] + # _ssl_connect_future should be defined in SSLIOStream + # but it's here so we can clean it up in _signal_closed + # TODO: refactor that so subclasses can add additional futures + # to be cancelled. + self._ssl_connect_future = None # type: Optional[Future[SSLIOStream]] + self._connecting = False + self._state = None # type: Optional[int] + self._closed = False + + def fileno(self) -> Union[int, ioloop._Selectable]: + """Returns the file descriptor for this stream.""" + raise NotImplementedError() + + def close_fd(self) -> None: + """Closes the file underlying this stream. + + ``close_fd`` is called by `BaseIOStream` and should not be called + elsewhere; other users should call `close` instead. + """ + raise NotImplementedError() + + def write_to_fd(self, data: memoryview) -> int: + """Attempts to write ``data`` to the underlying file. + + Returns the number of bytes written. + """ + raise NotImplementedError() + + def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: + """Attempts to read from the underlying file. + + Reads up to ``len(buf)`` bytes, storing them in the buffer. + Returns the number of bytes read. Returns None if there was + nothing to read (the socket returned `~errno.EWOULDBLOCK` or + equivalent), and zero on EOF. + + .. versionchanged:: 5.0 + + Interface redesigned to take a buffer and return a number + of bytes instead of a freshly-allocated object. + """ + raise NotImplementedError() + + def get_fd_error(self) -> Optional[Exception]: + """Returns information about any error on the underlying file. + + This method is called after the `.IOLoop` has signaled an error on the + file descriptor, and should return an Exception (such as `socket.error` + with additional information, or None if no such information is + available. + """ + return None + + def read_until_regex(self, regex: bytes, max_bytes: int = None) -> Awaitable[bytes]: + """Asynchronously read until we have matched the given regex. + + The result includes the data that matches the regex and anything + that came before it. + + If ``max_bytes`` is not None, the connection will be closed + if more than ``max_bytes`` bytes have been read and the regex is + not satisfied. + + .. versionchanged:: 4.0 + Added the ``max_bytes`` argument. The ``callback`` argument is + now optional and a `.Future` will be returned if it is omitted. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + + """ + future = self._start_read() + self._read_regex = re.compile(regex) + self._read_max_bytes = max_bytes + try: + self._try_inline_read() + except UnsatisfiableReadError as e: + # Handle this the same way as in _handle_events. + gen_log.info("Unsatisfiable read, closing connection: %s" % e) + self.close(exc_info=e) + return future + except: + # Ensure that the future doesn't log an error because its + # failure was never examined. + future.add_done_callback(lambda f: f.exception()) + raise + return future + + def read_until(self, delimiter: bytes, max_bytes: int = None) -> Awaitable[bytes]: + """Asynchronously read until we have found the given delimiter. + + The result includes all the data read including the delimiter. + + If ``max_bytes`` is not None, the connection will be closed + if more than ``max_bytes`` bytes have been read and the delimiter + is not found. + + .. versionchanged:: 4.0 + Added the ``max_bytes`` argument. The ``callback`` argument is + now optional and a `.Future` will be returned if it is omitted. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + """ + future = self._start_read() + self._read_delimiter = delimiter + self._read_max_bytes = max_bytes + try: + self._try_inline_read() + except UnsatisfiableReadError as e: + # Handle this the same way as in _handle_events. + gen_log.info("Unsatisfiable read, closing connection: %s" % e) + self.close(exc_info=e) + return future + except: + future.add_done_callback(lambda f: f.exception()) + raise + return future + + def read_bytes(self, num_bytes: int, partial: bool = False) -> Awaitable[bytes]: + """Asynchronously read a number of bytes. + + If ``partial`` is true, data is returned as soon as we have + any bytes to return (but never more than ``num_bytes``) + + .. versionchanged:: 4.0 + Added the ``partial`` argument. The callback argument is now + optional and a `.Future` will be returned if it is omitted. + + .. versionchanged:: 6.0 + + The ``callback`` and ``streaming_callback`` arguments have + been removed. Use the returned `.Future` (and + ``partial=True`` for ``streaming_callback``) instead. + + """ + future = self._start_read() + assert isinstance(num_bytes, numbers.Integral) + self._read_bytes = num_bytes + self._read_partial = partial + try: + self._try_inline_read() + except: + future.add_done_callback(lambda f: f.exception()) + raise + return future + + def read_into(self, buf: bytearray, partial: bool = False) -> Awaitable[int]: + """Asynchronously read a number of bytes. + + ``buf`` must be a writable buffer into which data will be read. + + If ``partial`` is true, the callback is run as soon as any bytes + have been read. Otherwise, it is run when the ``buf`` has been + entirely filled with read data. + + .. versionadded:: 5.0 + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + + """ + future = self._start_read() + + # First copy data already in read buffer + available_bytes = self._read_buffer_size + n = len(buf) + if available_bytes >= n: + end = self._read_buffer_pos + n + buf[:] = memoryview(self._read_buffer)[self._read_buffer_pos : end] + del self._read_buffer[:end] + self._after_user_read_buffer = self._read_buffer + elif available_bytes > 0: + buf[:available_bytes] = memoryview(self._read_buffer)[ + self._read_buffer_pos : + ] + + # Set up the supplied buffer as our temporary read buffer. + # The original (if it had any data remaining) has been + # saved for later. + self._user_read_buffer = True + self._read_buffer = buf + self._read_buffer_pos = 0 + self._read_buffer_size = available_bytes + self._read_bytes = n + self._read_partial = partial + + try: + self._try_inline_read() + except: + future.add_done_callback(lambda f: f.exception()) + raise + return future + + def read_until_close(self) -> Awaitable[bytes]: + """Asynchronously reads all data from the socket until it is closed. + + This will buffer all available data until ``max_buffer_size`` + is reached. If flow control or cancellation are desired, use a + loop with `read_bytes(partial=True) <.read_bytes>` instead. + + .. versionchanged:: 4.0 + The callback argument is now optional and a `.Future` will + be returned if it is omitted. + + .. versionchanged:: 6.0 + + The ``callback`` and ``streaming_callback`` arguments have + been removed. Use the returned `.Future` (and `read_bytes` + with ``partial=True`` for ``streaming_callback``) instead. + + """ + future = self._start_read() + if self.closed(): + self._finish_read(self._read_buffer_size, False) + return future + self._read_until_close = True + try: + self._try_inline_read() + except: + future.add_done_callback(lambda f: f.exception()) + raise + return future + + def write(self, data: Union[bytes, memoryview]) -> "Future[None]": + """Asynchronously write the given data to this stream. + + This method returns a `.Future` that resolves (with a result + of ``None``) when the write has been completed. + + The ``data`` argument may be of type `bytes` or `memoryview`. + + .. versionchanged:: 4.0 + Now returns a `.Future` if no callback is given. + + .. versionchanged:: 4.5 + Added support for `memoryview` arguments. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + + """ + self._check_closed() + if data: + if ( + self.max_write_buffer_size is not None + and len(self._write_buffer) + len(data) > self.max_write_buffer_size + ): + raise StreamBufferFullError("Reached maximum write buffer size") + self._write_buffer.append(data) + self._total_write_index += len(data) + future = Future() # type: Future[None] + future.add_done_callback(lambda f: f.exception()) + self._write_futures.append((self._total_write_index, future)) + if not self._connecting: + self._handle_write() + if self._write_buffer: + self._add_io_state(self.io_loop.WRITE) + self._maybe_add_error_listener() + return future + + def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None: + """Call the given callback when the stream is closed. + + This mostly is not necessary for applications that use the + `.Future` interface; all outstanding ``Futures`` will resolve + with a `StreamClosedError` when the stream is closed. However, + it is still useful as a way to signal that the stream has been + closed while no other read or write is in progress. + + Unlike other callback-based interfaces, ``set_close_callback`` + was not removed in Tornado 6.0. + """ + self._close_callback = callback + self._maybe_add_error_listener() + + def close( + self, + exc_info: Union[ + None, + bool, + BaseException, + Tuple[ + "Optional[Type[BaseException]]", + Optional[BaseException], + Optional[TracebackType], + ], + ] = False, + ) -> None: + """Close this stream. + + If ``exc_info`` is true, set the ``error`` attribute to the current + exception from `sys.exc_info` (or if ``exc_info`` is a tuple, + use that instead of `sys.exc_info`). + """ + if not self.closed(): + if exc_info: + if isinstance(exc_info, tuple): + self.error = exc_info[1] + elif isinstance(exc_info, BaseException): + self.error = exc_info + else: + exc_info = sys.exc_info() + if any(exc_info): + self.error = exc_info[1] + if self._read_until_close: + self._read_until_close = False + self._finish_read(self._read_buffer_size, False) + if self._state is not None: + self.io_loop.remove_handler(self.fileno()) + self._state = None + self.close_fd() + self._closed = True + self._signal_closed() + + def _signal_closed(self) -> None: + futures = [] # type: List[Future] + if self._read_future is not None: + futures.append(self._read_future) + self._read_future = None + futures += [future for _, future in self._write_futures] + self._write_futures.clear() + if self._connect_future is not None: + futures.append(self._connect_future) + self._connect_future = None + for future in futures: + if not future.done(): + future.set_exception(StreamClosedError(real_error=self.error)) + # Reference the exception to silence warnings. Annoyingly, + # this raises if the future was cancelled, but just + # returns any other error. + try: + future.exception() + except asyncio.CancelledError: + pass + if self._ssl_connect_future is not None: + # _ssl_connect_future expects to see the real exception (typically + # an ssl.SSLError), not just StreamClosedError. + if not self._ssl_connect_future.done(): + if self.error is not None: + self._ssl_connect_future.set_exception(self.error) + else: + self._ssl_connect_future.set_exception(StreamClosedError()) + self._ssl_connect_future.exception() + self._ssl_connect_future = None + if self._close_callback is not None: + cb = self._close_callback + self._close_callback = None + self.io_loop.add_callback(cb) + # Clear the buffers so they can be cleared immediately even + # if the IOStream object is kept alive by a reference cycle. + # TODO: Clear the read buffer too; it currently breaks some tests. + self._write_buffer = None # type: ignore + + def reading(self) -> bool: + """Returns ``True`` if we are currently reading from the stream.""" + return self._read_future is not None + + def writing(self) -> bool: + """Returns ``True`` if we are currently writing to the stream.""" + return bool(self._write_buffer) + + def closed(self) -> bool: + """Returns ``True`` if the stream has been closed.""" + return self._closed + + def set_nodelay(self, value: bool) -> None: + """Sets the no-delay flag for this stream. + + By default, data written to TCP streams may be held for a time + to make the most efficient use of bandwidth (according to + Nagle's algorithm). The no-delay flag requests that data be + written as soon as possible, even if doing so would consume + additional bandwidth. + + This flag is currently defined only for TCP-based ``IOStreams``. + + .. versionadded:: 3.1 + """ + pass + + def _handle_connect(self) -> None: + raise NotImplementedError() + + def _handle_events(self, fd: Union[int, ioloop._Selectable], events: int) -> None: + if self.closed(): + gen_log.warning("Got events for closed stream %s", fd) + return + try: + if self._connecting: + # Most IOLoops will report a write failed connect + # with the WRITE event, but SelectIOLoop reports a + # READ as well so we must check for connecting before + # either. + self._handle_connect() + if self.closed(): + return + if events & self.io_loop.READ: + self._handle_read() + if self.closed(): + return + if events & self.io_loop.WRITE: + self._handle_write() + if self.closed(): + return + if events & self.io_loop.ERROR: + self.error = self.get_fd_error() + # We may have queued up a user callback in _handle_read or + # _handle_write, so don't close the IOStream until those + # callbacks have had a chance to run. + self.io_loop.add_callback(self.close) + return + state = self.io_loop.ERROR + if self.reading(): + state |= self.io_loop.READ + if self.writing(): + state |= self.io_loop.WRITE + if state == self.io_loop.ERROR and self._read_buffer_size == 0: + # If the connection is idle, listen for reads too so + # we can tell if the connection is closed. If there is + # data in the read buffer we won't run the close callback + # yet anyway, so we don't need to listen in this case. + state |= self.io_loop.READ + if state != self._state: + assert ( + self._state is not None + ), "shouldn't happen: _handle_events without self._state" + self._state = state + self.io_loop.update_handler(self.fileno(), self._state) + except UnsatisfiableReadError as e: + gen_log.info("Unsatisfiable read, closing connection: %s" % e) + self.close(exc_info=e) + except Exception as e: + gen_log.error("Uncaught exception, closing connection.", exc_info=True) + self.close(exc_info=e) + raise + + def _read_to_buffer_loop(self) -> Optional[int]: + # This method is called from _handle_read and _try_inline_read. + if self._read_bytes is not None: + target_bytes = self._read_bytes # type: Optional[int] + elif self._read_max_bytes is not None: + target_bytes = self._read_max_bytes + elif self.reading(): + # For read_until without max_bytes, or + # read_until_close, read as much as we can before + # scanning for the delimiter. + target_bytes = None + else: + target_bytes = 0 + next_find_pos = 0 + while not self.closed(): + # Read from the socket until we get EWOULDBLOCK or equivalent. + # SSL sockets do some internal buffering, and if the data is + # sitting in the SSL object's buffer select() and friends + # can't see it; the only way to find out if it's there is to + # try to read it. + if self._read_to_buffer() == 0: + break + + # If we've read all the bytes we can use, break out of + # this loop. + + # If we've reached target_bytes, we know we're done. + if target_bytes is not None and self._read_buffer_size >= target_bytes: + break + + # Otherwise, we need to call the more expensive find_read_pos. + # It's inefficient to do this on every read, so instead + # do it on the first read and whenever the read buffer + # size has doubled. + if self._read_buffer_size >= next_find_pos: + pos = self._find_read_pos() + if pos is not None: + return pos + next_find_pos = self._read_buffer_size * 2 + return self._find_read_pos() + + def _handle_read(self) -> None: + try: + pos = self._read_to_buffer_loop() + except UnsatisfiableReadError: + raise + except asyncio.CancelledError: + raise + except Exception as e: + gen_log.warning("error on read: %s" % e) + self.close(exc_info=e) + return + if pos is not None: + self._read_from_buffer(pos) + + def _start_read(self) -> Future: + self._check_closed() # Before reading, check that stream is not closed. + assert self._read_future is None, "Already reading" + self._read_future = Future() + return self._read_future + + def _finish_read(self, size: int, streaming: bool) -> None: + if self._user_read_buffer: + self._read_buffer = self._after_user_read_buffer or bytearray() + self._after_user_read_buffer = None + self._read_buffer_pos = 0 + self._read_buffer_size = len(self._read_buffer) + self._user_read_buffer = False + result = size # type: Union[int, bytes] + else: + result = self._consume(size) + if self._read_future is not None: + future = self._read_future + self._read_future = None + future_set_result_unless_cancelled(future, result) + self._maybe_add_error_listener() + + def _try_inline_read(self) -> None: + """Attempt to complete the current read operation from buffered data. + + If the read can be completed without blocking, schedules the + read callback on the next IOLoop iteration; otherwise starts + listening for reads on the socket. + """ + # See if we've already got the data from a previous read + pos = self._find_read_pos() + if pos is not None: + self._read_from_buffer(pos) + return + self._check_closed() + pos = self._read_to_buffer_loop() + if pos is not None: + self._read_from_buffer(pos) + return + # We couldn't satisfy the read inline, so make sure we're + # listening for new data unless the stream is closed. + if not self.closed(): + self._add_io_state(ioloop.IOLoop.READ) + + def _read_to_buffer(self) -> Optional[int]: + """Reads from the socket and appends the result to the read buffer. + + Returns the number of bytes read. Returns 0 if there is nothing + to read (i.e. the read returns EWOULDBLOCK or equivalent). On + error closes the socket and raises an exception. + """ + try: + while True: + try: + if self._user_read_buffer: + buf = memoryview(self._read_buffer)[ + self._read_buffer_size : + ] # type: Union[memoryview, bytearray] + else: + buf = bytearray(self.read_chunk_size) + bytes_read = self.read_from_fd(buf) + except (socket.error, IOError, OSError) as e: + if errno_from_exception(e) == errno.EINTR: + continue + # ssl.SSLError is a subclass of socket.error + if self._is_connreset(e): + # Treat ECONNRESET as a connection close rather than + # an error to minimize log spam (the exception will + # be available on self.error for apps that care). + self.close(exc_info=e) + return None + self.close(exc_info=e) + raise + break + if bytes_read is None: + return 0 + elif bytes_read == 0: + self.close() + return 0 + if not self._user_read_buffer: + self._read_buffer += memoryview(buf)[:bytes_read] + self._read_buffer_size += bytes_read + finally: + # Break the reference to buf so we don't waste a chunk's worth of + # memory in case an exception hangs on to our stack frame. + del buf + if self._read_buffer_size > self.max_buffer_size: + gen_log.error("Reached maximum read buffer size") + self.close() + raise StreamBufferFullError("Reached maximum read buffer size") + return bytes_read + + def _read_from_buffer(self, pos: int) -> None: + """Attempts to complete the currently-pending read from the buffer. + + The argument is either a position in the read buffer or None, + as returned by _find_read_pos. + """ + self._read_bytes = self._read_delimiter = self._read_regex = None + self._read_partial = False + self._finish_read(pos, False) + + def _find_read_pos(self) -> Optional[int]: + """Attempts to find a position in the read buffer that satisfies + the currently-pending read. + + Returns a position in the buffer if the current read can be satisfied, + or None if it cannot. + """ + if self._read_bytes is not None and ( + self._read_buffer_size >= self._read_bytes + or (self._read_partial and self._read_buffer_size > 0) + ): + num_bytes = min(self._read_bytes, self._read_buffer_size) + return num_bytes + elif self._read_delimiter is not None: + # Multi-byte delimiters (e.g. '\r\n') may straddle two + # chunks in the read buffer, so we can't easily find them + # without collapsing the buffer. However, since protocols + # using delimited reads (as opposed to reads of a known + # length) tend to be "line" oriented, the delimiter is likely + # to be in the first few chunks. Merge the buffer gradually + # since large merges are relatively expensive and get undone in + # _consume(). + if self._read_buffer: + loc = self._read_buffer.find( + self._read_delimiter, self._read_buffer_pos + ) + if loc != -1: + loc -= self._read_buffer_pos + delimiter_len = len(self._read_delimiter) + self._check_max_bytes(self._read_delimiter, loc + delimiter_len) + return loc + delimiter_len + self._check_max_bytes(self._read_delimiter, self._read_buffer_size) + elif self._read_regex is not None: + if self._read_buffer: + m = self._read_regex.search(self._read_buffer, self._read_buffer_pos) + if m is not None: + loc = m.end() - self._read_buffer_pos + self._check_max_bytes(self._read_regex, loc) + return loc + self._check_max_bytes(self._read_regex, self._read_buffer_size) + return None + + def _check_max_bytes(self, delimiter: Union[bytes, Pattern], size: int) -> None: + if self._read_max_bytes is not None and size > self._read_max_bytes: + raise UnsatisfiableReadError( + "delimiter %r not found within %d bytes" + % (delimiter, self._read_max_bytes) + ) + + def _handle_write(self) -> None: + while True: + size = len(self._write_buffer) + if not size: + break + assert size > 0 + try: + if _WINDOWS: + # On windows, socket.send blows up if given a + # write buffer that's too large, instead of just + # returning the number of bytes it was able to + # process. Therefore we must not call socket.send + # with more than 128KB at a time. + size = 128 * 1024 + + num_bytes = self.write_to_fd(self._write_buffer.peek(size)) + if num_bytes == 0: + break + self._write_buffer.advance(num_bytes) + self._total_write_done_index += num_bytes + except (socket.error, IOError, OSError) as e: + if e.args[0] in _ERRNO_WOULDBLOCK: + break + else: + if not self._is_connreset(e): + # Broken pipe errors are usually caused by connection + # reset, and its better to not log EPIPE errors to + # minimize log spam + gen_log.warning("Write error on %s: %s", self.fileno(), e) + self.close(exc_info=e) + return + + while self._write_futures: + index, future = self._write_futures[0] + if index > self._total_write_done_index: + break + self._write_futures.popleft() + future_set_result_unless_cancelled(future, None) + + def _consume(self, loc: int) -> bytes: + # Consume loc bytes from the read buffer and return them + if loc == 0: + return b"" + assert loc <= self._read_buffer_size + # Slice the bytearray buffer into bytes, without intermediate copying + b = ( + memoryview(self._read_buffer)[ + self._read_buffer_pos : self._read_buffer_pos + loc + ] + ).tobytes() + self._read_buffer_pos += loc + self._read_buffer_size -= loc + # Amortized O(1) shrink + # (this heuristic is implemented natively in Python 3.4+ + # but is replicated here for Python 2) + if self._read_buffer_pos > self._read_buffer_size: + del self._read_buffer[: self._read_buffer_pos] + self._read_buffer_pos = 0 + return b + + def _check_closed(self) -> None: + if self.closed(): + raise StreamClosedError(real_error=self.error) + + def _maybe_add_error_listener(self) -> None: + # This method is part of an optimization: to detect a connection that + # is closed when we're not actively reading or writing, we must listen + # for read events. However, it is inefficient to do this when the + # connection is first established because we are going to read or write + # immediately anyway. Instead, we insert checks at various times to + # see if the connection is idle and add the read listener then. + if self._state is None or self._state == ioloop.IOLoop.ERROR: + if ( + not self.closed() + and self._read_buffer_size == 0 + and self._close_callback is not None + ): + self._add_io_state(ioloop.IOLoop.READ) + + def _add_io_state(self, state: int) -> None: + """Adds `state` (IOLoop.{READ,WRITE} flags) to our event handler. + + Implementation notes: Reads and writes have a fast path and a + slow path. The fast path reads synchronously from socket + buffers, while the slow path uses `_add_io_state` to schedule + an IOLoop callback. + + To detect closed connections, we must have called + `_add_io_state` at some point, but we want to delay this as + much as possible so we don't have to set an `IOLoop.ERROR` + listener that will be overwritten by the next slow-path + operation. If a sequence of fast-path ops do not end in a + slow-path op, (e.g. for an @asynchronous long-poll request), + we must add the error handler. + + TODO: reevaluate this now that callbacks are gone. + + """ + if self.closed(): + # connection has been closed, so there can be no future events + return + if self._state is None: + self._state = ioloop.IOLoop.ERROR | state + self.io_loop.add_handler(self.fileno(), self._handle_events, self._state) + elif not self._state & state: + self._state = self._state | state + self.io_loop.update_handler(self.fileno(), self._state) + + def _is_connreset(self, exc: BaseException) -> bool: + """Return ``True`` if exc is ECONNRESET or equivalent. + + May be overridden in subclasses. + """ + return ( + isinstance(exc, (socket.error, IOError)) + and errno_from_exception(exc) in _ERRNO_CONNRESET + ) + + +class IOStream(BaseIOStream): + r"""Socket-based `IOStream` implementation. + + This class supports the read and write methods from `BaseIOStream` + plus a `connect` method. + + The ``socket`` parameter may either be connected or unconnected. + For server operations the socket is the result of calling + `socket.accept <socket.socket.accept>`. For client operations the + socket is created with `socket.socket`, and may either be + connected before passing it to the `IOStream` or connected with + `IOStream.connect`. + + A very simple (and broken) HTTP client using this class: + + .. testcode:: + + import tornado.ioloop + import tornado.iostream + import socket + + async def main(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + stream = tornado.iostream.IOStream(s) + await stream.connect(("friendfeed.com", 80)) + await stream.write(b"GET / HTTP/1.0\r\nHost: friendfeed.com\r\n\r\n") + header_data = await stream.read_until(b"\r\n\r\n") + headers = {} + for line in header_data.split(b"\r\n"): + parts = line.split(b":") + if len(parts) == 2: + headers[parts[0].strip()] = parts[1].strip() + body_data = await stream.read_bytes(int(headers[b"Content-Length"])) + print(body_data) + stream.close() + + if __name__ == '__main__': + tornado.ioloop.IOLoop.current().run_sync(main) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + stream = tornado.iostream.IOStream(s) + stream.connect(("friendfeed.com", 80), send_request) + tornado.ioloop.IOLoop.current().start() + + .. testoutput:: + :hide: + + """ + + def __init__(self, socket: socket.socket, *args: Any, **kwargs: Any) -> None: + self.socket = socket + self.socket.setblocking(False) + super(IOStream, self).__init__(*args, **kwargs) + + def fileno(self) -> Union[int, ioloop._Selectable]: + return self.socket + + def close_fd(self) -> None: + self.socket.close() + self.socket = None # type: ignore + + def get_fd_error(self) -> Optional[Exception]: + errno = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + return socket.error(errno, os.strerror(errno)) + + def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: + try: + return self.socket.recv_into(buf, len(buf)) + except socket.error as e: + if e.args[0] in _ERRNO_WOULDBLOCK: + return None + else: + raise + finally: + del buf + + def write_to_fd(self, data: memoryview) -> int: + try: + return self.socket.send(data) # type: ignore + finally: + # Avoid keeping to data, which can be a memoryview. + # See https://github.com/tornadoweb/tornado/pull/2008 + del data + + def connect( + self: _IOStreamType, address: tuple, server_hostname: str = None + ) -> "Future[_IOStreamType]": + """Connects the socket to a remote address without blocking. + + May only be called if the socket passed to the constructor was + not previously connected. The address parameter is in the + same format as for `socket.connect <socket.socket.connect>` for + the type of socket passed to the IOStream constructor, + e.g. an ``(ip, port)`` tuple. Hostnames are accepted here, + but will be resolved synchronously and block the IOLoop. + If you have a hostname instead of an IP address, the `.TCPClient` + class is recommended instead of calling this method directly. + `.TCPClient` will do asynchronous DNS resolution and handle + both IPv4 and IPv6. + + If ``callback`` is specified, it will be called with no + arguments when the connection is completed; if not this method + returns a `.Future` (whose result after a successful + connection will be the stream itself). + + In SSL mode, the ``server_hostname`` parameter will be used + for certificate validation (unless disabled in the + ``ssl_options``) and SNI (if supported; requires Python + 2.7.9+). + + Note that it is safe to call `IOStream.write + <BaseIOStream.write>` while the connection is pending, in + which case the data will be written as soon as the connection + is ready. Calling `IOStream` read methods before the socket is + connected works on some platforms but is non-portable. + + .. versionchanged:: 4.0 + If no callback is given, returns a `.Future`. + + .. versionchanged:: 4.2 + SSL certificates are validated by default; pass + ``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a + suitably-configured `ssl.SSLContext` to the + `SSLIOStream` constructor to disable. + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + + """ + self._connecting = True + future = Future() # type: Future[_IOStreamType] + self._connect_future = typing.cast("Future[IOStream]", future) + try: + self.socket.connect(address) + except socket.error as e: + # In non-blocking mode we expect connect() to raise an + # exception with EINPROGRESS or EWOULDBLOCK. + # + # On freebsd, other errors such as ECONNREFUSED may be + # returned immediately when attempting to connect to + # localhost, so handle them the same way as an error + # reported later in _handle_connect. + if ( + errno_from_exception(e) not in _ERRNO_INPROGRESS + and errno_from_exception(e) not in _ERRNO_WOULDBLOCK + ): + if future is None: + gen_log.warning( + "Connect error on fd %s: %s", self.socket.fileno(), e + ) + self.close(exc_info=e) + return future + self._add_io_state(self.io_loop.WRITE) + return future + + def start_tls( + self, + server_side: bool, + ssl_options: Union[Dict[str, Any], ssl.SSLContext] = None, + server_hostname: str = None, + ) -> Awaitable["SSLIOStream"]: + """Convert this `IOStream` to an `SSLIOStream`. + + This enables protocols that begin in clear-text mode and + switch to SSL after some initial negotiation (such as the + ``STARTTLS`` extension to SMTP and IMAP). + + This method cannot be used if there are outstanding reads + or writes on the stream, or if there is any data in the + IOStream's buffer (data in the operating system's socket + buffer is allowed). This means it must generally be used + immediately after reading or writing the last clear-text + data. It can also be used immediately after connecting, + before any reads or writes. + + The ``ssl_options`` argument may be either an `ssl.SSLContext` + object or a dictionary of keyword arguments for the + `ssl.wrap_socket` function. The ``server_hostname`` argument + will be used for certificate validation unless disabled + in the ``ssl_options``. + + This method returns a `.Future` whose result is the new + `SSLIOStream`. After this method has been called, + any other operation on the original stream is undefined. + + If a close callback is defined on this stream, it will be + transferred to the new stream. + + .. versionadded:: 4.0 + + .. versionchanged:: 4.2 + SSL certificates are validated by default; pass + ``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a + suitably-configured `ssl.SSLContext` to disable. + """ + if ( + self._read_future + or self._write_futures + or self._connect_future + or self._closed + or self._read_buffer + or self._write_buffer + ): + raise ValueError("IOStream is not idle; cannot convert to SSL") + if ssl_options is None: + if server_side: + ssl_options = _server_ssl_defaults + else: + ssl_options = _client_ssl_defaults + + socket = self.socket + self.io_loop.remove_handler(socket) + self.socket = None # type: ignore + socket = ssl_wrap_socket( + socket, + ssl_options, + server_hostname=server_hostname, + server_side=server_side, + do_handshake_on_connect=False, + ) + orig_close_callback = self._close_callback + self._close_callback = None + + future = Future() # type: Future[SSLIOStream] + ssl_stream = SSLIOStream(socket, ssl_options=ssl_options) + ssl_stream.set_close_callback(orig_close_callback) + ssl_stream._ssl_connect_future = future + ssl_stream.max_buffer_size = self.max_buffer_size + ssl_stream.read_chunk_size = self.read_chunk_size + return future + + def _handle_connect(self) -> None: + try: + err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + except socket.error as e: + # Hurd doesn't allow SO_ERROR for loopback sockets because all + # errors for such sockets are reported synchronously. + if errno_from_exception(e) == errno.ENOPROTOOPT: + err = 0 + if err != 0: + self.error = socket.error(err, os.strerror(err)) + # IOLoop implementations may vary: some of them return + # an error state before the socket becomes writable, so + # in that case a connection failure would be handled by the + # error path in _handle_events instead of here. + if self._connect_future is None: + gen_log.warning( + "Connect error on fd %s: %s", + self.socket.fileno(), + errno.errorcode[err], + ) + self.close() + return + if self._connect_future is not None: + future = self._connect_future + self._connect_future = None + future_set_result_unless_cancelled(future, self) + self._connecting = False + + def set_nodelay(self, value: bool) -> None: + if self.socket is not None and self.socket.family in ( + socket.AF_INET, + socket.AF_INET6, + ): + try: + self.socket.setsockopt( + socket.IPPROTO_TCP, socket.TCP_NODELAY, 1 if value else 0 + ) + except socket.error as e: + # Sometimes setsockopt will fail if the socket is closed + # at the wrong time. This can happen with HTTPServer + # resetting the value to ``False`` between requests. + if e.errno != errno.EINVAL and not self._is_connreset(e): + raise + + +class SSLIOStream(IOStream): + """A utility class to write to and read from a non-blocking SSL socket. + + If the socket passed to the constructor is already connected, + it should be wrapped with:: + + ssl.wrap_socket(sock, do_handshake_on_connect=False, **kwargs) + + before constructing the `SSLIOStream`. Unconnected sockets will be + wrapped when `IOStream.connect` is finished. + """ + + socket = None # type: ssl.SSLSocket + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """The ``ssl_options`` keyword argument may either be an + `ssl.SSLContext` object or a dictionary of keywords arguments + for `ssl.wrap_socket` + """ + self._ssl_options = kwargs.pop("ssl_options", _client_ssl_defaults) + super(SSLIOStream, self).__init__(*args, **kwargs) + self._ssl_accepting = True + self._handshake_reading = False + self._handshake_writing = False + self._server_hostname = None # type: Optional[str] + + # If the socket is already connected, attempt to start the handshake. + try: + self.socket.getpeername() + except socket.error: + pass + else: + # Indirectly start the handshake, which will run on the next + # IOLoop iteration and then the real IO state will be set in + # _handle_events. + self._add_io_state(self.io_loop.WRITE) + + def reading(self) -> bool: + return self._handshake_reading or super(SSLIOStream, self).reading() + + def writing(self) -> bool: + return self._handshake_writing or super(SSLIOStream, self).writing() + + def _do_ssl_handshake(self) -> None: + # Based on code from test_ssl.py in the python stdlib + try: + self._handshake_reading = False + self._handshake_writing = False + self.socket.do_handshake() + except ssl.SSLError as err: + if err.args[0] == ssl.SSL_ERROR_WANT_READ: + self._handshake_reading = True + return + elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: + self._handshake_writing = True + return + elif err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): + return self.close(exc_info=err) + elif err.args[0] == ssl.SSL_ERROR_SSL: + try: + peer = self.socket.getpeername() + except Exception: + peer = "(not connected)" + gen_log.warning( + "SSL Error on %s %s: %s", self.socket.fileno(), peer, err + ) + return self.close(exc_info=err) + raise + except socket.error as err: + # Some port scans (e.g. nmap in -sT mode) have been known + # to cause do_handshake to raise EBADF and ENOTCONN, so make + # those errors quiet as well. + # https://groups.google.com/forum/?fromgroups#!topic/python-tornado/ApucKJat1_0 + # Errno 0 is also possible in some cases (nc -z). + # https://github.com/tornadoweb/tornado/issues/2504 + if self._is_connreset(err) or err.args[0] in ( + 0, + errno.EBADF, + errno.ENOTCONN, + ): + return self.close(exc_info=err) + raise + except AttributeError as err: + # On Linux, if the connection was reset before the call to + # wrap_socket, do_handshake will fail with an + # AttributeError. + return self.close(exc_info=err) + else: + self._ssl_accepting = False + if not self._verify_cert(self.socket.getpeercert()): + self.close() + return + self._finish_ssl_connect() + + def _finish_ssl_connect(self) -> None: + if self._ssl_connect_future is not None: + future = self._ssl_connect_future + self._ssl_connect_future = None + future_set_result_unless_cancelled(future, self) + + def _verify_cert(self, peercert: Any) -> bool: + """Returns ``True`` if peercert is valid according to the configured + validation mode and hostname. + + The ssl handshake already tested the certificate for a valid + CA signature; the only thing that remains is to check + the hostname. + """ + if isinstance(self._ssl_options, dict): + verify_mode = self._ssl_options.get("cert_reqs", ssl.CERT_NONE) + elif isinstance(self._ssl_options, ssl.SSLContext): + verify_mode = self._ssl_options.verify_mode + assert verify_mode in (ssl.CERT_NONE, ssl.CERT_REQUIRED, ssl.CERT_OPTIONAL) + if verify_mode == ssl.CERT_NONE or self._server_hostname is None: + return True + cert = self.socket.getpeercert() + if cert is None and verify_mode == ssl.CERT_REQUIRED: + gen_log.warning("No SSL certificate given") + return False + try: + ssl.match_hostname(peercert, self._server_hostname) + except ssl.CertificateError as e: + gen_log.warning("Invalid SSL certificate: %s" % e) + return False + else: + return True + + def _handle_read(self) -> None: + if self._ssl_accepting: + self._do_ssl_handshake() + return + super(SSLIOStream, self)._handle_read() + + def _handle_write(self) -> None: + if self._ssl_accepting: + self._do_ssl_handshake() + return + super(SSLIOStream, self)._handle_write() + + def connect( + self, address: Tuple, server_hostname: str = None + ) -> "Future[SSLIOStream]": + self._server_hostname = server_hostname + # Ignore the result of connect(). If it fails, + # wait_for_handshake will raise an error too. This is + # necessary for the old semantics of the connect callback + # (which takes no arguments). In 6.0 this can be refactored to + # be a regular coroutine. + # TODO: This is trickier than it looks, since if write() + # is called with a connect() pending, we want the connect + # to resolve before the write. Or do we care about this? + # (There's a test for it, but I think in practice users + # either wait for the connect before performing a write or + # they don't care about the connect Future at all) + fut = super(SSLIOStream, self).connect(address) + fut.add_done_callback(lambda f: f.exception()) + return self.wait_for_handshake() + + def _handle_connect(self) -> None: + # Call the superclass method to check for errors. + super(SSLIOStream, self)._handle_connect() + if self.closed(): + return + # When the connection is complete, wrap the socket for SSL + # traffic. Note that we do this by overriding _handle_connect + # instead of by passing a callback to super().connect because + # user callbacks are enqueued asynchronously on the IOLoop, + # but since _handle_events calls _handle_connect immediately + # followed by _handle_write we need this to be synchronous. + # + # The IOLoop will get confused if we swap out self.socket while the + # fd is registered, so remove it now and re-register after + # wrap_socket(). + self.io_loop.remove_handler(self.socket) + old_state = self._state + assert old_state is not None + self._state = None + self.socket = ssl_wrap_socket( + self.socket, + self._ssl_options, + server_hostname=self._server_hostname, + do_handshake_on_connect=False, + ) + self._add_io_state(old_state) + + def wait_for_handshake(self) -> "Future[SSLIOStream]": + """Wait for the initial SSL handshake to complete. + + If a ``callback`` is given, it will be called with no + arguments once the handshake is complete; otherwise this + method returns a `.Future` which will resolve to the + stream itself after the handshake is complete. + + Once the handshake is complete, information such as + the peer's certificate and NPN/ALPN selections may be + accessed on ``self.socket``. + + This method is intended for use on server-side streams + or after using `IOStream.start_tls`; it should not be used + with `IOStream.connect` (which already waits for the + handshake to complete). It may only be called once per stream. + + .. versionadded:: 4.2 + + .. versionchanged:: 6.0 + + The ``callback`` argument was removed. Use the returned + `.Future` instead. + + """ + if self._ssl_connect_future is not None: + raise RuntimeError("Already waiting") + future = self._ssl_connect_future = Future() + if not self._ssl_accepting: + self._finish_ssl_connect() + return future + + def write_to_fd(self, data: memoryview) -> int: + try: + return self.socket.send(data) # type: ignore + except ssl.SSLError as e: + if e.args[0] == ssl.SSL_ERROR_WANT_WRITE: + # In Python 3.5+, SSLSocket.send raises a WANT_WRITE error if + # the socket is not writeable; we need to transform this into + # an EWOULDBLOCK socket.error or a zero return value, + # either of which will be recognized by the caller of this + # method. Prior to Python 3.5, an unwriteable socket would + # simply return 0 bytes written. + return 0 + raise + finally: + # Avoid keeping to data, which can be a memoryview. + # See https://github.com/tornadoweb/tornado/pull/2008 + del data + + def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: + try: + if self._ssl_accepting: + # If the handshake hasn't finished yet, there can't be anything + # to read (attempting to read may or may not raise an exception + # depending on the SSL version) + return None + try: + return self.socket.recv_into(buf, len(buf)) + except ssl.SSLError as e: + # SSLError is a subclass of socket.error, so this except + # block must come first. + if e.args[0] == ssl.SSL_ERROR_WANT_READ: + return None + else: + raise + except socket.error as e: + if e.args[0] in _ERRNO_WOULDBLOCK: + return None + else: + raise + finally: + del buf + + def _is_connreset(self, e: BaseException) -> bool: + if isinstance(e, ssl.SSLError) and e.args[0] == ssl.SSL_ERROR_EOF: + return True + return super(SSLIOStream, self)._is_connreset(e) + + +class PipeIOStream(BaseIOStream): + """Pipe-based `IOStream` implementation. + + The constructor takes an integer file descriptor (such as one returned + by `os.pipe`) rather than an open file object. Pipes are generally + one-way, so a `PipeIOStream` can be used for reading or writing but not + both. + """ + + def __init__(self, fd: int, *args: Any, **kwargs: Any) -> None: + self.fd = fd + self._fio = io.FileIO(self.fd, "r+") + _set_nonblocking(fd) + super(PipeIOStream, self).__init__(*args, **kwargs) + + def fileno(self) -> int: + return self.fd + + def close_fd(self) -> None: + self._fio.close() + + def write_to_fd(self, data: memoryview) -> int: + try: + return os.write(self.fd, data) # type: ignore + finally: + # Avoid keeping to data, which can be a memoryview. + # See https://github.com/tornadoweb/tornado/pull/2008 + del data + + def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: + try: + return self._fio.readinto(buf) # type: ignore + except (IOError, OSError) as e: + if errno_from_exception(e) == errno.EBADF: + # If the writing half of a pipe is closed, select will + # report it as readable but reads will fail with EBADF. + self.close(exc_info=e) + return None + else: + raise + finally: + del buf + + +def doctests() -> Any: + import doctest + + return doctest.DocTestSuite() diff --git a/venv/lib/python3.7/site-packages/tornado/locale.py b/venv/lib/python3.7/site-packages/tornado/locale.py new file mode 100644 index 0000000..b72d81b --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/locale.py @@ -0,0 +1,564 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2009 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. + +"""Translation methods for generating localized strings. + +To load a locale and generate a translated string:: + + user_locale = tornado.locale.get("es_LA") + print(user_locale.translate("Sign out")) + +`tornado.locale.get()` returns the closest matching locale, not necessarily the +specific locale you requested. You can support pluralization with +additional arguments to `~Locale.translate()`, e.g.:: + + people = [...] + message = user_locale.translate( + "%(list)s is online", "%(list)s are online", len(people)) + print(message % {"list": user_locale.list(people)}) + +The first string is chosen if ``len(people) == 1``, otherwise the second +string is chosen. + +Applications should call one of `load_translations` (which uses a simple +CSV format) or `load_gettext_translations` (which uses the ``.mo`` format +supported by `gettext` and related tools). If neither method is called, +the `Locale.translate` method will simply return the original string. +""" + +import codecs +import csv +import datetime +import gettext +import os +import re + +from tornado import escape +from tornado.log import gen_log + +from tornado._locale_data import LOCALE_NAMES + +from typing import Iterable, Any, Union, Dict + +_default_locale = "en_US" +_translations = {} # type: Dict[str, Any] +_supported_locales = frozenset([_default_locale]) +_use_gettext = False +CONTEXT_SEPARATOR = "\x04" + + +def get(*locale_codes: str) -> "Locale": + """Returns the closest match for the given locale codes. + + We iterate over all given locale codes in order. If we have a tight + or a loose match for the code (e.g., "en" for "en_US"), we return + the locale. Otherwise we move to the next code in the list. + + By default we return ``en_US`` if no translations are found for any of + the specified locales. You can change the default locale with + `set_default_locale()`. + """ + return Locale.get_closest(*locale_codes) + + +def set_default_locale(code: str) -> None: + """Sets the default locale. + + The default locale is assumed to be the language used for all strings + in the system. The translations loaded from disk are mappings from + the default locale to the destination locale. Consequently, you don't + need to create a translation file for the default locale. + """ + global _default_locale + global _supported_locales + _default_locale = code + _supported_locales = frozenset(list(_translations.keys()) + [_default_locale]) + + +def load_translations(directory: str, encoding: str = None) -> None: + """Loads translations from CSV files in a directory. + + Translations are strings with optional Python-style named placeholders + (e.g., ``My name is %(name)s``) and their associated translations. + + The directory should have translation files of the form ``LOCALE.csv``, + e.g. ``es_GT.csv``. The CSV files should have two or three columns: string, + translation, and an optional plural indicator. Plural indicators should + be one of "plural" or "singular". A given string can have both singular + and plural forms. For example ``%(name)s liked this`` may have a + different verb conjugation depending on whether %(name)s is one + name or a list of names. There should be two rows in the CSV file for + that string, one with plural indicator "singular", and one "plural". + For strings with no verbs that would change on translation, simply + use "unknown" or the empty string (or don't include the column at all). + + The file is read using the `csv` module in the default "excel" dialect. + In this format there should not be spaces after the commas. + + If no ``encoding`` parameter is given, the encoding will be + detected automatically (among UTF-8 and UTF-16) if the file + contains a byte-order marker (BOM), defaulting to UTF-8 if no BOM + is present. + + Example translation ``es_LA.csv``:: + + "I love you","Te amo" + "%(name)s liked this","A %(name)s les gustó esto","plural" + "%(name)s liked this","A %(name)s le gustó esto","singular" + + .. versionchanged:: 4.3 + Added ``encoding`` parameter. Added support for BOM-based encoding + detection, UTF-16, and UTF-8-with-BOM. + """ + global _translations + global _supported_locales + _translations = {} + for path in os.listdir(directory): + if not path.endswith(".csv"): + continue + locale, extension = path.split(".") + if not re.match("[a-z]+(_[A-Z]+)?$", locale): + gen_log.error( + "Unrecognized locale %r (path: %s)", + locale, + os.path.join(directory, path), + ) + continue + full_path = os.path.join(directory, path) + if encoding is None: + # Try to autodetect encoding based on the BOM. + with open(full_path, "rb") as bf: + data = bf.read(len(codecs.BOM_UTF16_LE)) + if data in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + encoding = "utf-16" + else: + # utf-8-sig is "utf-8 with optional BOM". It's discouraged + # in most cases but is common with CSV files because Excel + # cannot read utf-8 files without a BOM. + encoding = "utf-8-sig" + # python 3: csv.reader requires a file open in text mode. + # Specify an encoding to avoid dependence on $LANG environment variable. + with open(full_path, encoding=encoding) as f: + _translations[locale] = {} + for i, row in enumerate(csv.reader(f)): + if not row or len(row) < 2: + continue + row = [escape.to_unicode(c).strip() for c in row] + english, translation = row[:2] + if len(row) > 2: + plural = row[2] or "unknown" + else: + plural = "unknown" + if plural not in ("plural", "singular", "unknown"): + gen_log.error( + "Unrecognized plural indicator %r in %s line %d", + plural, + path, + i + 1, + ) + continue + _translations[locale].setdefault(plural, {})[english] = translation + _supported_locales = frozenset(list(_translations.keys()) + [_default_locale]) + gen_log.debug("Supported locales: %s", sorted(_supported_locales)) + + +def load_gettext_translations(directory: str, domain: str) -> None: + """Loads translations from `gettext`'s locale tree + + Locale tree is similar to system's ``/usr/share/locale``, like:: + + {directory}/{lang}/LC_MESSAGES/{domain}.mo + + Three steps are required to have your app translated: + + 1. Generate POT translation file:: + + xgettext --language=Python --keyword=_:1,2 -d mydomain file1.py file2.html etc + + 2. Merge against existing POT file:: + + msgmerge old.po mydomain.po > new.po + + 3. Compile:: + + msgfmt mydomain.po -o {directory}/pt_BR/LC_MESSAGES/mydomain.mo + """ + import gettext + + global _translations + global _supported_locales + global _use_gettext + _translations = {} + for lang in os.listdir(directory): + if lang.startswith("."): + continue # skip .svn, etc + if os.path.isfile(os.path.join(directory, lang)): + continue + try: + os.stat(os.path.join(directory, lang, "LC_MESSAGES", domain + ".mo")) + _translations[lang] = gettext.translation( + domain, directory, languages=[lang] + ) + except Exception as e: + gen_log.error("Cannot load translation for '%s': %s", lang, str(e)) + continue + _supported_locales = frozenset(list(_translations.keys()) + [_default_locale]) + _use_gettext = True + gen_log.debug("Supported locales: %s", sorted(_supported_locales)) + + +def get_supported_locales() -> Iterable[str]: + """Returns a list of all the supported locale codes.""" + return _supported_locales + + +class Locale(object): + """Object representing a locale. + + After calling one of `load_translations` or `load_gettext_translations`, + call `get` or `get_closest` to get a Locale object. + """ + + _cache = {} # type: Dict[str, Locale] + + @classmethod + def get_closest(cls, *locale_codes: str) -> "Locale": + """Returns the closest match for the given locale code.""" + for code in locale_codes: + if not code: + continue + code = code.replace("-", "_") + parts = code.split("_") + if len(parts) > 2: + continue + elif len(parts) == 2: + code = parts[0].lower() + "_" + parts[1].upper() + if code in _supported_locales: + return cls.get(code) + if parts[0].lower() in _supported_locales: + return cls.get(parts[0].lower()) + return cls.get(_default_locale) + + @classmethod + def get(cls, code: str) -> "Locale": + """Returns the Locale for the given locale code. + + If it is not supported, we raise an exception. + """ + if code not in cls._cache: + assert code in _supported_locales + translations = _translations.get(code, None) + if translations is None: + locale = CSVLocale(code, {}) # type: Locale + elif _use_gettext: + locale = GettextLocale(code, translations) + else: + locale = CSVLocale(code, translations) + cls._cache[code] = locale + return cls._cache[code] + + def __init__(self, code: str) -> None: + self.code = code + self.name = LOCALE_NAMES.get(code, {}).get("name", u"Unknown") + self.rtl = False + for prefix in ["fa", "ar", "he"]: + if self.code.startswith(prefix): + self.rtl = True + break + + # Initialize strings for date formatting + _ = self.translate + self._months = [ + _("January"), + _("February"), + _("March"), + _("April"), + _("May"), + _("June"), + _("July"), + _("August"), + _("September"), + _("October"), + _("November"), + _("December"), + ] + self._weekdays = [ + _("Monday"), + _("Tuesday"), + _("Wednesday"), + _("Thursday"), + _("Friday"), + _("Saturday"), + _("Sunday"), + ] + + def translate( + self, message: str, plural_message: str = None, count: int = None + ) -> str: + """Returns the translation for the given message for this locale. + + If ``plural_message`` is given, you must also provide + ``count``. We return ``plural_message`` when ``count != 1``, + and we return the singular form for the given message when + ``count == 1``. + """ + raise NotImplementedError() + + def pgettext( + self, context: str, message: str, plural_message: str = None, count: int = None + ) -> str: + raise NotImplementedError() + + def format_date( + self, + date: Union[int, float, datetime.datetime], + gmt_offset: int = 0, + relative: bool = True, + shorter: bool = False, + full_format: bool = False, + ) -> str: + """Formats the given date (which should be GMT). + + By default, we return a relative time (e.g., "2 minutes ago"). You + can return an absolute date string with ``relative=False``. + + You can force a full format date ("July 10, 1980") with + ``full_format=True``. + + This method is primarily intended for dates in the past. + For dates in the future, we fall back to full format. + """ + if isinstance(date, (int, float)): + date = datetime.datetime.utcfromtimestamp(date) + now = datetime.datetime.utcnow() + if date > now: + if relative and (date - now).seconds < 60: + # Due to click skew, things are some things slightly + # in the future. Round timestamps in the immediate + # future down to now in relative mode. + date = now + else: + # Otherwise, future dates always use the full format. + full_format = True + local_date = date - datetime.timedelta(minutes=gmt_offset) + local_now = now - datetime.timedelta(minutes=gmt_offset) + local_yesterday = local_now - datetime.timedelta(hours=24) + difference = now - date + seconds = difference.seconds + days = difference.days + + _ = self.translate + format = None + if not full_format: + if relative and days == 0: + if seconds < 50: + return _("1 second ago", "%(seconds)d seconds ago", seconds) % { + "seconds": seconds + } + + if seconds < 50 * 60: + minutes = round(seconds / 60.0) + return _("1 minute ago", "%(minutes)d minutes ago", minutes) % { + "minutes": minutes + } + + hours = round(seconds / (60.0 * 60)) + return _("1 hour ago", "%(hours)d hours ago", hours) % {"hours": hours} + + if days == 0: + format = _("%(time)s") + elif days == 1 and local_date.day == local_yesterday.day and relative: + format = _("yesterday") if shorter else _("yesterday at %(time)s") + elif days < 5: + format = _("%(weekday)s") if shorter else _("%(weekday)s at %(time)s") + elif days < 334: # 11mo, since confusing for same month last year + format = ( + _("%(month_name)s %(day)s") + if shorter + else _("%(month_name)s %(day)s at %(time)s") + ) + + if format is None: + format = ( + _("%(month_name)s %(day)s, %(year)s") + if shorter + else _("%(month_name)s %(day)s, %(year)s at %(time)s") + ) + + tfhour_clock = self.code not in ("en", "en_US", "zh_CN") + if tfhour_clock: + str_time = "%d:%02d" % (local_date.hour, local_date.minute) + elif self.code == "zh_CN": + str_time = "%s%d:%02d" % ( + (u"\u4e0a\u5348", u"\u4e0b\u5348")[local_date.hour >= 12], + local_date.hour % 12 or 12, + local_date.minute, + ) + else: + str_time = "%d:%02d %s" % ( + local_date.hour % 12 or 12, + local_date.minute, + ("am", "pm")[local_date.hour >= 12], + ) + + return format % { + "month_name": self._months[local_date.month - 1], + "weekday": self._weekdays[local_date.weekday()], + "day": str(local_date.day), + "year": str(local_date.year), + "time": str_time, + } + + def format_day( + self, date: datetime.datetime, gmt_offset: int = 0, dow: bool = True + ) -> bool: + """Formats the given date as a day of week. + + Example: "Monday, January 22". You can remove the day of week with + ``dow=False``. + """ + local_date = date - datetime.timedelta(minutes=gmt_offset) + _ = self.translate + if dow: + return _("%(weekday)s, %(month_name)s %(day)s") % { + "month_name": self._months[local_date.month - 1], + "weekday": self._weekdays[local_date.weekday()], + "day": str(local_date.day), + } + else: + return _("%(month_name)s %(day)s") % { + "month_name": self._months[local_date.month - 1], + "day": str(local_date.day), + } + + def list(self, parts: Any) -> str: + """Returns a comma-separated list for the given list of parts. + + The format is, e.g., "A, B and C", "A and B" or just "A" for lists + of size 1. + """ + _ = self.translate + if len(parts) == 0: + return "" + if len(parts) == 1: + return parts[0] + comma = u" \u0648 " if self.code.startswith("fa") else u", " + return _("%(commas)s and %(last)s") % { + "commas": comma.join(parts[:-1]), + "last": parts[len(parts) - 1], + } + + def friendly_number(self, value: int) -> str: + """Returns a comma-separated number for the given integer.""" + if self.code not in ("en", "en_US"): + return str(value) + s = str(value) + parts = [] + while s: + parts.append(s[-3:]) + s = s[:-3] + return ",".join(reversed(parts)) + + +class CSVLocale(Locale): + """Locale implementation using tornado's CSV translation format.""" + + def __init__(self, code: str, translations: Dict[str, Dict[str, str]]) -> None: + self.translations = translations + super(CSVLocale, self).__init__(code) + + def translate( + self, message: str, plural_message: str = None, count: int = None + ) -> str: + if plural_message is not None: + assert count is not None + if count != 1: + message = plural_message + message_dict = self.translations.get("plural", {}) + else: + message_dict = self.translations.get("singular", {}) + else: + message_dict = self.translations.get("unknown", {}) + return message_dict.get(message, message) + + def pgettext( + self, context: str, message: str, plural_message: str = None, count: int = None + ) -> str: + if self.translations: + gen_log.warning("pgettext is not supported by CSVLocale") + return self.translate(message, plural_message, count) + + +class GettextLocale(Locale): + """Locale implementation using the `gettext` module.""" + + def __init__(self, code: str, translations: gettext.NullTranslations) -> None: + self.ngettext = translations.ngettext + self.gettext = translations.gettext + # self.gettext must exist before __init__ is called, since it + # calls into self.translate + super(GettextLocale, self).__init__(code) + + def translate( + self, message: str, plural_message: str = None, count: int = None + ) -> str: + if plural_message is not None: + assert count is not None + return self.ngettext(message, plural_message, count) + else: + return self.gettext(message) + + def pgettext( + self, context: str, message: str, plural_message: str = None, count: int = None + ) -> str: + """Allows to set context for translation, accepts plural forms. + + Usage example:: + + pgettext("law", "right") + pgettext("good", "right") + + Plural message example:: + + pgettext("organization", "club", "clubs", len(clubs)) + pgettext("stick", "club", "clubs", len(clubs)) + + To generate POT file with context, add following options to step 1 + of `load_gettext_translations` sequence:: + + xgettext [basic options] --keyword=pgettext:1c,2 --keyword=pgettext:1c,2,3 + + .. versionadded:: 4.2 + """ + if plural_message is not None: + assert count is not None + msgs_with_ctxt = ( + "%s%s%s" % (context, CONTEXT_SEPARATOR, message), + "%s%s%s" % (context, CONTEXT_SEPARATOR, plural_message), + count, + ) + result = self.ngettext(*msgs_with_ctxt) + if CONTEXT_SEPARATOR in result: + # Translation not found + result = self.ngettext(message, plural_message, count) + return result + else: + msg_with_ctxt = "%s%s%s" % (context, CONTEXT_SEPARATOR, message) + result = self.gettext(msg_with_ctxt) + if CONTEXT_SEPARATOR in result: + # Translation not found + result = message + return result diff --git a/venv/lib/python3.7/site-packages/tornado/locks.py b/venv/lib/python3.7/site-packages/tornado/locks.py new file mode 100644 index 0000000..699bb30 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/locks.py @@ -0,0 +1,570 @@ +# Copyright 2015 The Tornado Authors +# +# 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 collections +from concurrent.futures import CancelledError +import datetime +import types + +from tornado import gen, ioloop +from tornado.concurrent import Future, future_set_result_unless_cancelled + +from typing import Union, Optional, Type, Any, Awaitable +import typing + +if typing.TYPE_CHECKING: + from typing import Deque, Set # noqa: F401 + +__all__ = ["Condition", "Event", "Semaphore", "BoundedSemaphore", "Lock"] + + +class _TimeoutGarbageCollector(object): + """Base class for objects that periodically clean up timed-out waiters. + + Avoids memory leak in a common pattern like: + + while True: + yield condition.wait(short_timeout) + print('looping....') + """ + + def __init__(self) -> None: + self._waiters = collections.deque() # type: Deque[Future] + self._timeouts = 0 + + def _garbage_collect(self) -> None: + # Occasionally clear timed-out waiters. + self._timeouts += 1 + if self._timeouts > 100: + self._timeouts = 0 + self._waiters = collections.deque(w for w in self._waiters if not w.done()) + + +class Condition(_TimeoutGarbageCollector): + """A condition allows one or more coroutines to wait until notified. + + Like a standard `threading.Condition`, but does not need an underlying lock + that is acquired and released. + + With a `Condition`, coroutines can wait to be notified by other coroutines: + + .. testcode:: + + from tornado import gen + from tornado.ioloop import IOLoop + from tornado.locks import Condition + + condition = Condition() + + async def waiter(): + print("I'll wait right here") + await condition.wait() + print("I'm done waiting") + + async def notifier(): + print("About to notify") + condition.notify() + print("Done notifying") + + async def runner(): + # Wait for waiter() and notifier() in parallel + await gen.multi([waiter(), notifier()]) + + IOLoop.current().run_sync(runner) + + .. testoutput:: + + I'll wait right here + About to notify + Done notifying + I'm done waiting + + `wait` takes an optional ``timeout`` argument, which is either an absolute + timestamp:: + + io_loop = IOLoop.current() + + # Wait up to 1 second for a notification. + await condition.wait(timeout=io_loop.time() + 1) + + ...or a `datetime.timedelta` for a timeout relative to the current time:: + + # Wait up to 1 second. + await condition.wait(timeout=datetime.timedelta(seconds=1)) + + The method returns False if there's no notification before the deadline. + + .. versionchanged:: 5.0 + Previously, waiters could be notified synchronously from within + `notify`. Now, the notification will always be received on the + next iteration of the `.IOLoop`. + """ + + def __init__(self) -> None: + super(Condition, self).__init__() + self.io_loop = ioloop.IOLoop.current() + + def __repr__(self) -> str: + result = "<%s" % (self.__class__.__name__,) + if self._waiters: + result += " waiters[%s]" % len(self._waiters) + return result + ">" + + def wait(self, timeout: Union[float, datetime.timedelta] = None) -> Awaitable[bool]: + """Wait for `.notify`. + + Returns a `.Future` that resolves ``True`` if the condition is notified, + or ``False`` after a timeout. + """ + waiter = Future() # type: Future[bool] + self._waiters.append(waiter) + if timeout: + + def on_timeout() -> None: + if not waiter.done(): + future_set_result_unless_cancelled(waiter, False) + self._garbage_collect() + + io_loop = ioloop.IOLoop.current() + timeout_handle = io_loop.add_timeout(timeout, on_timeout) + waiter.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle)) + return waiter + + def notify(self, n: int = 1) -> None: + """Wake ``n`` waiters.""" + waiters = [] # Waiters we plan to run right now. + while n and self._waiters: + waiter = self._waiters.popleft() + if not waiter.done(): # Might have timed out. + n -= 1 + waiters.append(waiter) + + for waiter in waiters: + future_set_result_unless_cancelled(waiter, True) + + def notify_all(self) -> None: + """Wake all waiters.""" + self.notify(len(self._waiters)) + + +class Event(object): + """An event blocks coroutines until its internal flag is set to True. + + Similar to `threading.Event`. + + A coroutine can wait for an event to be set. Once it is set, calls to + ``yield event.wait()`` will not block unless the event has been cleared: + + .. testcode:: + + from tornado import gen + from tornado.ioloop import IOLoop + from tornado.locks import Event + + event = Event() + + async def waiter(): + print("Waiting for event") + await event.wait() + print("Not waiting this time") + await event.wait() + print("Done") + + async def setter(): + print("About to set the event") + event.set() + + async def runner(): + await gen.multi([waiter(), setter()]) + + IOLoop.current().run_sync(runner) + + .. testoutput:: + + Waiting for event + About to set the event + Not waiting this time + Done + """ + + def __init__(self) -> None: + self._value = False + self._waiters = set() # type: Set[Future[None]] + + def __repr__(self) -> str: + return "<%s %s>" % ( + self.__class__.__name__, + "set" if self.is_set() else "clear", + ) + + def is_set(self) -> bool: + """Return ``True`` if the internal flag is true.""" + return self._value + + def set(self) -> None: + """Set the internal flag to ``True``. All waiters are awakened. + + Calling `.wait` once the flag is set will not block. + """ + if not self._value: + self._value = True + + for fut in self._waiters: + if not fut.done(): + fut.set_result(None) + + def clear(self) -> None: + """Reset the internal flag to ``False``. + + Calls to `.wait` will block until `.set` is called. + """ + self._value = False + + def wait(self, timeout: Union[float, datetime.timedelta] = None) -> Awaitable[None]: + """Block until the internal flag is true. + + Returns an awaitable, which raises `tornado.util.TimeoutError` after a + timeout. + """ + fut = Future() # type: Future[None] + if self._value: + fut.set_result(None) + return fut + self._waiters.add(fut) + fut.add_done_callback(lambda fut: self._waiters.remove(fut)) + if timeout is None: + return fut + else: + timeout_fut = gen.with_timeout( + timeout, fut, quiet_exceptions=(CancelledError,) + ) + # This is a slightly clumsy workaround for the fact that + # gen.with_timeout doesn't cancel its futures. Cancelling + # fut will remove it from the waiters list. + timeout_fut.add_done_callback( + lambda tf: fut.cancel() if not fut.done() else None + ) + return timeout_fut + + +class _ReleasingContextManager(object): + """Releases a Lock or Semaphore at the end of a "with" statement. + + with (yield semaphore.acquire()): + pass + + # Now semaphore.release() has been called. + """ + + def __init__(self, obj: Any) -> None: + self._obj = obj + + def __enter__(self) -> None: + pass + + def __exit__( + self, + exc_type: "Optional[Type[BaseException]]", + exc_val: Optional[BaseException], + exc_tb: Optional[types.TracebackType], + ) -> None: + self._obj.release() + + +class Semaphore(_TimeoutGarbageCollector): + """A lock that can be acquired a fixed number of times before blocking. + + A Semaphore manages a counter representing the number of `.release` calls + minus the number of `.acquire` calls, plus an initial value. The `.acquire` + method blocks if necessary until it can return without making the counter + negative. + + Semaphores limit access to a shared resource. To allow access for two + workers at a time: + + .. testsetup:: semaphore + + from collections import deque + + from tornado import gen + from tornado.ioloop import IOLoop + from tornado.concurrent import Future + + # Ensure reliable doctest output: resolve Futures one at a time. + futures_q = deque([Future() for _ in range(3)]) + + async def simulator(futures): + for f in futures: + # simulate the asynchronous passage of time + await gen.sleep(0) + await gen.sleep(0) + f.set_result(None) + + IOLoop.current().add_callback(simulator, list(futures_q)) + + def use_some_resource(): + return futures_q.popleft() + + .. testcode:: semaphore + + from tornado import gen + from tornado.ioloop import IOLoop + from tornado.locks import Semaphore + + sem = Semaphore(2) + + async def worker(worker_id): + await sem.acquire() + try: + print("Worker %d is working" % worker_id) + await use_some_resource() + finally: + print("Worker %d is done" % worker_id) + sem.release() + + async def runner(): + # Join all workers. + await gen.multi([worker(i) for i in range(3)]) + + IOLoop.current().run_sync(runner) + + .. testoutput:: semaphore + + Worker 0 is working + Worker 1 is working + Worker 0 is done + Worker 2 is working + Worker 1 is done + Worker 2 is done + + Workers 0 and 1 are allowed to run concurrently, but worker 2 waits until + the semaphore has been released once, by worker 0. + + The semaphore can be used as an async context manager:: + + async def worker(worker_id): + async with sem: + print("Worker %d is working" % worker_id) + await use_some_resource() + + # Now the semaphore has been released. + print("Worker %d is done" % worker_id) + + For compatibility with older versions of Python, `.acquire` is a + context manager, so ``worker`` could also be written as:: + + @gen.coroutine + def worker(worker_id): + with (yield sem.acquire()): + print("Worker %d is working" % worker_id) + yield use_some_resource() + + # Now the semaphore has been released. + print("Worker %d is done" % worker_id) + + .. versionchanged:: 4.3 + Added ``async with`` support in Python 3.5. + + """ + + def __init__(self, value: int = 1) -> None: + super(Semaphore, self).__init__() + if value < 0: + raise ValueError("semaphore initial value must be >= 0") + + self._value = value + + def __repr__(self) -> str: + res = super(Semaphore, self).__repr__() + extra = ( + "locked" if self._value == 0 else "unlocked,value:{0}".format(self._value) + ) + if self._waiters: + extra = "{0},waiters:{1}".format(extra, len(self._waiters)) + return "<{0} [{1}]>".format(res[1:-1], extra) + + def release(self) -> None: + """Increment the counter and wake one waiter.""" + self._value += 1 + while self._waiters: + waiter = self._waiters.popleft() + if not waiter.done(): + self._value -= 1 + + # If the waiter is a coroutine paused at + # + # with (yield semaphore.acquire()): + # + # then the context manager's __exit__ calls release() at the end + # of the "with" block. + waiter.set_result(_ReleasingContextManager(self)) + break + + def acquire( + self, timeout: Union[float, datetime.timedelta] = None + ) -> Awaitable[_ReleasingContextManager]: + """Decrement the counter. Returns an awaitable. + + Block if the counter is zero and wait for a `.release`. The awaitable + raises `.TimeoutError` after the deadline. + """ + waiter = Future() # type: Future[_ReleasingContextManager] + if self._value > 0: + self._value -= 1 + waiter.set_result(_ReleasingContextManager(self)) + else: + self._waiters.append(waiter) + if timeout: + + def on_timeout() -> None: + if not waiter.done(): + waiter.set_exception(gen.TimeoutError()) + self._garbage_collect() + + io_loop = ioloop.IOLoop.current() + timeout_handle = io_loop.add_timeout(timeout, on_timeout) + waiter.add_done_callback( + lambda _: io_loop.remove_timeout(timeout_handle) + ) + return waiter + + def __enter__(self) -> None: + raise RuntimeError("Use 'async with' instead of 'with' for Semaphore") + + def __exit__( + self, + typ: "Optional[Type[BaseException]]", + value: Optional[BaseException], + traceback: Optional[types.TracebackType], + ) -> None: + self.__enter__() + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + typ: "Optional[Type[BaseException]]", + value: Optional[BaseException], + tb: Optional[types.TracebackType], + ) -> None: + self.release() + + +class BoundedSemaphore(Semaphore): + """A semaphore that prevents release() being called too many times. + + If `.release` would increment the semaphore's value past the initial + value, it raises `ValueError`. Semaphores are mostly used to guard + resources with limited capacity, so a semaphore released too many times + is a sign of a bug. + """ + + def __init__(self, value: int = 1) -> None: + super(BoundedSemaphore, self).__init__(value=value) + self._initial_value = value + + def release(self) -> None: + """Increment the counter and wake one waiter.""" + if self._value >= self._initial_value: + raise ValueError("Semaphore released too many times") + super(BoundedSemaphore, self).release() + + +class Lock(object): + """A lock for coroutines. + + A Lock begins unlocked, and `acquire` locks it immediately. While it is + locked, a coroutine that yields `acquire` waits until another coroutine + calls `release`. + + Releasing an unlocked lock raises `RuntimeError`. + + A Lock can be used as an async context manager with the ``async + with`` statement: + + >>> from tornado import locks + >>> lock = locks.Lock() + >>> + >>> async def f(): + ... async with lock: + ... # Do something holding the lock. + ... pass + ... + ... # Now the lock is released. + + For compatibility with older versions of Python, the `.acquire` + method asynchronously returns a regular context manager: + + >>> async def f2(): + ... with (yield lock.acquire()): + ... # Do something holding the lock. + ... pass + ... + ... # Now the lock is released. + + .. versionchanged:: 4.3 + Added ``async with`` support in Python 3.5. + + """ + + def __init__(self) -> None: + self._block = BoundedSemaphore(value=1) + + def __repr__(self) -> str: + return "<%s _block=%s>" % (self.__class__.__name__, self._block) + + def acquire( + self, timeout: Union[float, datetime.timedelta] = None + ) -> Awaitable[_ReleasingContextManager]: + """Attempt to lock. Returns an awaitable. + + Returns an awaitable, which raises `tornado.util.TimeoutError` after a + timeout. + """ + return self._block.acquire(timeout) + + def release(self) -> None: + """Unlock. + + The first coroutine in line waiting for `acquire` gets the lock. + + If not locked, raise a `RuntimeError`. + """ + try: + self._block.release() + except ValueError: + raise RuntimeError("release unlocked lock") + + def __enter__(self) -> None: + raise RuntimeError("Use `async with` instead of `with` for Lock") + + def __exit__( + self, + typ: "Optional[Type[BaseException]]", + value: Optional[BaseException], + tb: Optional[types.TracebackType], + ) -> None: + self.__enter__() + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + typ: "Optional[Type[BaseException]]", + value: Optional[BaseException], + tb: Optional[types.TracebackType], + ) -> None: + self.release() diff --git a/venv/lib/python3.7/site-packages/tornado/log.py b/venv/lib/python3.7/site-packages/tornado/log.py new file mode 100644 index 0000000..435cd71 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/log.py @@ -0,0 +1,336 @@ +# +# 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. +"""Logging support for Tornado. + +Tornado uses three logger streams: + +* ``tornado.access``: Per-request logging for Tornado's HTTP servers (and + potentially other servers in the future) +* ``tornado.application``: Logging of errors from application code (i.e. + uncaught exceptions from callbacks) +* ``tornado.general``: General-purpose logging, including any errors + or warnings from Tornado itself. + +These streams may be configured independently using the standard library's +`logging` module. For example, you may wish to send ``tornado.access`` logs +to a separate file for analysis. +""" +import logging +import logging.handlers +import sys + +from tornado.escape import _unicode +from tornado.util import unicode_type, basestring_type + +try: + import colorama # type: ignore +except ImportError: + colorama = None + +try: + import curses +except ImportError: + curses = None # type: ignore + +from typing import Dict, Any, cast + +# Logger objects for internal tornado use +access_log = logging.getLogger("tornado.access") +app_log = logging.getLogger("tornado.application") +gen_log = logging.getLogger("tornado.general") + + +def _stderr_supports_color() -> bool: + try: + if hasattr(sys.stderr, "isatty") and sys.stderr.isatty(): + if curses: + curses.setupterm() + if curses.tigetnum("colors") > 0: + return True + elif colorama: + if sys.stderr is getattr( + colorama.initialise, "wrapped_stderr", object() + ): + return True + except Exception: + # Very broad exception handling because it's always better to + # fall back to non-colored logs than to break at startup. + pass + return False + + +def _safe_unicode(s: Any) -> str: + try: + return _unicode(s) + except UnicodeDecodeError: + return repr(s) + + +class LogFormatter(logging.Formatter): + """Log formatter used in Tornado. + + Key features of this formatter are: + + * Color support when logging to a terminal that supports it. + * Timestamps on every log line. + * Robust against str/bytes encoding problems. + + This formatter is enabled automatically by + `tornado.options.parse_command_line` or `tornado.options.parse_config_file` + (unless ``--logging=none`` is used). + + Color support on Windows versions that do not support ANSI color codes is + enabled by use of the colorama__ library. Applications that wish to use + this must first initialize colorama with a call to ``colorama.init``. + See the colorama documentation for details. + + __ https://pypi.python.org/pypi/colorama + + .. versionchanged:: 4.5 + Added support for ``colorama``. Changed the constructor + signature to be compatible with `logging.config.dictConfig`. + """ + + DEFAULT_FORMAT = "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s" # noqa: E501 + DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S" + DEFAULT_COLORS = { + logging.DEBUG: 4, # Blue + logging.INFO: 2, # Green + logging.WARNING: 3, # Yellow + logging.ERROR: 1, # Red + } + + def __init__( + self, + fmt: str = DEFAULT_FORMAT, + datefmt: str = DEFAULT_DATE_FORMAT, + style: str = "%", + color: bool = True, + colors: Dict[int, int] = DEFAULT_COLORS, + ) -> None: + r""" + :arg bool color: Enables color support. + :arg str 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 str 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._fmt = fmt + + self._colors = {} # type: Dict[int, str] + if color and _stderr_supports_color(): + if curses is not None: + fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or b"" + + for levelno, code in colors.items(): + # Convert the terminal control characters from + # bytes to unicode strings for easier use with the + # logging module. + self._colors[levelno] = unicode_type( + curses.tparm(fg_color, code), "ascii" + ) + self._normal = unicode_type(curses.tigetstr("sgr0"), "ascii") + else: + # If curses is not present (currently we'll only get here for + # colorama on windows), assume hard-coded ANSI color codes. + for levelno, code in colors.items(): + self._colors[levelno] = "\033[2;3%dm" % code + self._normal = "\033[0m" + else: + self._normal = "" + + def format(self, record: Any) -> str: + try: + message = record.getMessage() + assert isinstance(message, basestring_type) # guaranteed by logging + # Encoding notes: The logging module prefers to work with character + # strings, but only enforces that log messages are instances of + # basestring. In python 2, non-ascii bytestrings will make + # their way through the logging framework until they blow up with + # an unhelpful decoding error (with this formatter it happens + # when we attach the prefix, but there are other opportunities for + # exceptions further along in the framework). + # + # If a byte string makes it this far, convert it to unicode to + # ensure it will make it out to the logs. Use repr() as a fallback + # to ensure that all byte strings can be converted successfully, + # but don't do it by default so we don't add extra quotes to ascii + # bytestrings. This is a bit of a hacky place to do this, but + # it's worth it since the encoding errors that would otherwise + # result are so useless (and tornado is fond of using utf8-encoded + # byte strings wherever possible). + record.message = _safe_unicode(message) + except Exception as e: + record.message = "Bad message (%r): %r" % (e, record.__dict__) + + record.asctime = self.formatTime(record, cast(str, self.datefmt)) + + if record.levelno in self._colors: + record.color = self._colors[record.levelno] + record.end_color = self._normal + else: + record.color = record.end_color = "" + + formatted = self._fmt % record.__dict__ + + if record.exc_info: + if not record.exc_text: + record.exc_text = self.formatException(record.exc_info) + if record.exc_text: + # exc_text contains multiple lines. We need to _safe_unicode + # each line separately so that non-utf8 bytes don't cause + # all the newlines to turn into '\n'. + lines = [formatted.rstrip()] + lines.extend(_safe_unicode(ln) for ln in record.exc_text.split("\n")) + formatted = "\n".join(lines) + return formatted.replace("\n", "\n ") + + +def enable_pretty_logging(options: Any = None, logger: logging.Logger = None) -> None: + """Turns on formatted logging output as configured. + + This is called automatically by `tornado.options.parse_command_line` + and `tornado.options.parse_config_file`. + """ + if options is None: + import tornado.options + + options = tornado.options.options + if options.logging is None or options.logging.lower() == "none": + return + if logger is None: + logger = logging.getLogger() + logger.setLevel(getattr(logging, options.logging.upper())) + if options.log_file_prefix: + rotate_mode = options.log_rotate_mode + if rotate_mode == "size": + channel = logging.handlers.RotatingFileHandler( + filename=options.log_file_prefix, + maxBytes=options.log_file_max_size, + backupCount=options.log_file_num_backups, + encoding="utf-8", + ) # type: logging.Handler + elif rotate_mode == "time": + channel = logging.handlers.TimedRotatingFileHandler( + filename=options.log_file_prefix, + when=options.log_rotate_when, + interval=options.log_rotate_interval, + backupCount=options.log_file_num_backups, + encoding="utf-8", + ) + else: + error_message = ( + "The value of log_rotate_mode option should be " + + '"size" or "time", not "%s".' % rotate_mode + ) + raise ValueError(error_message) + channel.setFormatter(LogFormatter(color=False)) + logger.addHandler(channel) + + if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers): + # Set up color if we are in a tty and curses is installed + channel = logging.StreamHandler() + channel.setFormatter(LogFormatter()) + logger.addHandler(channel) + + +def define_logging_options(options: Any = None) -> None: + """Add logging-related flags to ``options``. + + These options are present automatically on the default options instance; + this method is only necessary if you have created your own `.OptionParser`. + + .. versionadded:: 4.2 + This function existed in prior versions but was broken and undocumented until 4.2. + """ + if options is None: + # late import to prevent cycle + import tornado.options + + options = tornado.options.options + options.define( + "logging", + default="info", + help=( + "Set the Python log level. If 'none', tornado won't touch the " + "logging configuration." + ), + metavar="debug|info|warning|error|none", + ) + options.define( + "log_to_stderr", + type=bool, + default=None, + help=( + "Send log output to stderr (colorized if possible). " + "By default use stderr if --log_file_prefix is not set and " + "no other logging is configured." + ), + ) + options.define( + "log_file_prefix", + type=str, + default=None, + metavar="PATH", + help=( + "Path prefix for log files. " + "Note that if you are running multiple tornado processes, " + "log_file_prefix must be different for each of them (e.g. " + "include the port number)" + ), + ) + options.define( + "log_file_max_size", + type=int, + default=100 * 1000 * 1000, + help="max size of log files before rollover", + ) + options.define( + "log_file_num_backups", type=int, default=10, help="number of log files to keep" + ) + + options.define( + "log_rotate_when", + type=str, + default="midnight", + help=( + "specify the type of TimedRotatingFileHandler interval " + "other options:('S', 'M', 'H', 'D', 'W0'-'W6')" + ), + ) + options.define( + "log_rotate_interval", + type=int, + default=1, + help="The interval value of timed rotating", + ) + + options.define( + "log_rotate_mode", + type=str, + default="size", + help="The mode of rotating files(time or size)", + ) + + options.add_parse_callback(lambda: enable_pretty_logging(options)) diff --git a/venv/lib/python3.7/site-packages/tornado/netutil.py b/venv/lib/python3.7/site-packages/tornado/netutil.py new file mode 100644 index 0000000..9338ced --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/netutil.py @@ -0,0 +1,614 @@ +# +# Copyright 2011 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. + +"""Miscellaneous network utility code.""" + +import concurrent.futures +import errno +import os +import sys +import socket +import ssl +import stat + +from tornado.concurrent import dummy_executor, run_on_executor +from tornado.ioloop import IOLoop +from tornado.platform.auto import set_close_exec +from tornado.util import Configurable, errno_from_exception + +import typing +from typing import List, Callable, Any, Type, Dict, Union, Tuple, Awaitable + +if typing.TYPE_CHECKING: + from asyncio import Future # noqa: F401 + +# Note that the naming of ssl.Purpose is confusing; the purpose +# of a context is to authentiate the opposite side of the connection. +_client_ssl_defaults = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) +_server_ssl_defaults = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) +if hasattr(ssl, "OP_NO_COMPRESSION"): + # See netutil.ssl_options_to_context + _client_ssl_defaults.options |= ssl.OP_NO_COMPRESSION + _server_ssl_defaults.options |= ssl.OP_NO_COMPRESSION + +# ThreadedResolver runs getaddrinfo on a thread. If the hostname is unicode, +# getaddrinfo attempts to import encodings.idna. If this is done at +# module-import time, the import lock is already held by the main thread, +# leading to deadlock. Avoid it by caching the idna encoder on the main +# thread now. +u"foo".encode("idna") + +# For undiagnosed reasons, 'latin1' codec may also need to be preloaded. +u"foo".encode("latin1") + +# These errnos indicate that a non-blocking operation must be retried +# at a later time. On most platforms they're the same value, but on +# some they differ. +_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN) + +if hasattr(errno, "WSAEWOULDBLOCK"): + _ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,) # type: ignore + +# Default backlog used when calling sock.listen() +_DEFAULT_BACKLOG = 128 + + +def bind_sockets( + port: int, + address: str = None, + family: socket.AddressFamily = socket.AF_UNSPEC, + backlog: int = _DEFAULT_BACKLOG, + flags: int = None, + reuse_port: bool = False, +) -> List[socket.socket]: + """Creates listening sockets bound to the given port and address. + + Returns a list of socket objects (multiple sockets are returned if + the given address maps to multiple IP addresses, which is most common + for mixed IPv4 and IPv6 use). + + Address may be either an IP address or hostname. If it's a hostname, + the server will listen on all IP addresses associated with the + name. Address may be an empty string or None to listen on all + available interfaces. Family may be set to either `socket.AF_INET` + or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise + both will be used if available. + + The ``backlog`` argument has the same meaning as for + `socket.listen() <socket.socket.listen>`. + + ``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like + ``socket.AI_PASSIVE | socket.AI_NUMERICHOST``. + + ``reuse_port`` option sets ``SO_REUSEPORT`` option for every socket + in the list. If your platform doesn't support this option ValueError will + be raised. + """ + if reuse_port and not hasattr(socket, "SO_REUSEPORT"): + raise ValueError("the platform doesn't support SO_REUSEPORT") + + sockets = [] + if address == "": + address = None + if not socket.has_ipv6 and family == socket.AF_UNSPEC: + # Python can be compiled with --disable-ipv6, which causes + # operations on AF_INET6 sockets to fail, but does not + # automatically exclude those results from getaddrinfo + # results. + # http://bugs.python.org/issue16208 + family = socket.AF_INET + if flags is None: + flags = socket.AI_PASSIVE + bound_port = None + unique_addresses = set() # type: set + for res in sorted( + socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags), + key=lambda x: x[0], + ): + if res in unique_addresses: + continue + + unique_addresses.add(res) + + af, socktype, proto, canonname, sockaddr = res + if ( + sys.platform == "darwin" + and address == "localhost" + and af == socket.AF_INET6 + and sockaddr[3] != 0 + ): + # Mac OS X includes a link-local address fe80::1%lo0 in the + # getaddrinfo results for 'localhost'. However, the firewall + # doesn't understand that this is a local address and will + # prompt for access (often repeatedly, due to an apparent + # bug in its ability to remember granting access to an + # application). Skip these addresses. + continue + try: + sock = socket.socket(af, socktype, proto) + except socket.error as e: + if errno_from_exception(e) == errno.EAFNOSUPPORT: + continue + raise + set_close_exec(sock.fileno()) + if os.name != "nt": + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + except socket.error as e: + if errno_from_exception(e) != errno.ENOPROTOOPT: + # Hurd doesn't support SO_REUSEADDR. + raise + if reuse_port: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + if af == socket.AF_INET6: + # On linux, ipv6 sockets accept ipv4 too by default, + # but this makes it impossible to bind to both + # 0.0.0.0 in ipv4 and :: in ipv6. On other systems, + # separate sockets *must* be used to listen for both ipv4 + # and ipv6. For consistency, always disable ipv4 on our + # ipv6 sockets and use a separate ipv4 socket when needed. + # + # Python 2.x on windows doesn't have IPPROTO_IPV6. + if hasattr(socket, "IPPROTO_IPV6"): + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) + + # automatic port allocation with port=None + # should bind on the same port on IPv4 and IPv6 + host, requested_port = sockaddr[:2] + if requested_port == 0 and bound_port is not None: + sockaddr = tuple([host, bound_port] + list(sockaddr[2:])) + + sock.setblocking(False) + sock.bind(sockaddr) + bound_port = sock.getsockname()[1] + sock.listen(backlog) + sockets.append(sock) + return sockets + + +if hasattr(socket, "AF_UNIX"): + + def bind_unix_socket( + file: str, mode: int = 0o600, backlog: int = _DEFAULT_BACKLOG + ) -> socket.socket: + """Creates a listening unix socket. + + If a socket with the given name already exists, it will be deleted. + If any other file with that name exists, an exception will be + raised. + + Returns a socket object (not a list of socket objects like + `bind_sockets`) + """ + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + set_close_exec(sock.fileno()) + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + except socket.error as e: + if errno_from_exception(e) != errno.ENOPROTOOPT: + # Hurd doesn't support SO_REUSEADDR + raise + sock.setblocking(False) + try: + st = os.stat(file) + except OSError as err: + if errno_from_exception(err) != errno.ENOENT: + raise + else: + if stat.S_ISSOCK(st.st_mode): + os.remove(file) + else: + raise ValueError("File %s exists and is not a socket", file) + sock.bind(file) + os.chmod(file, mode) + sock.listen(backlog) + return sock + + +def add_accept_handler( + sock: socket.socket, callback: Callable[[socket.socket, Any], None] +) -> Callable[[], None]: + """Adds an `.IOLoop` event handler to accept new connections on ``sock``. + + When a connection is accepted, ``callback(connection, address)`` will + be run (``connection`` is a socket object, and ``address`` is the + address of the other end of the connection). Note that this signature + is different from the ``callback(fd, events)`` signature used for + `.IOLoop` handlers. + + A callable is returned which, when called, will remove the `.IOLoop` + event handler and stop processing further incoming connections. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + + .. versionchanged:: 5.0 + A callable is returned (``None`` was returned before). + """ + io_loop = IOLoop.current() + removed = [False] + + def accept_handler(fd: socket.socket, events: int) -> None: + # More connections may come in while we're handling callbacks; + # to prevent starvation of other tasks we must limit the number + # of connections we accept at a time. Ideally we would accept + # up to the number of connections that were waiting when we + # entered this method, but this information is not available + # (and rearranging this method to call accept() as many times + # as possible before running any callbacks would have adverse + # effects on load balancing in multiprocess configurations). + # Instead, we use the (default) listen backlog as a rough + # heuristic for the number of connections we can reasonably + # accept at once. + for i in range(_DEFAULT_BACKLOG): + if removed[0]: + # The socket was probably closed + return + try: + connection, address = sock.accept() + except socket.error as e: + # _ERRNO_WOULDBLOCK indicate we have accepted every + # connection that is available. + if errno_from_exception(e) in _ERRNO_WOULDBLOCK: + return + # ECONNABORTED indicates that there was a connection + # but it was closed while still in the accept queue. + # (observed on FreeBSD). + if errno_from_exception(e) == errno.ECONNABORTED: + continue + raise + set_close_exec(connection.fileno()) + callback(connection, address) + + def remove_handler() -> None: + io_loop.remove_handler(sock) + removed[0] = True + + io_loop.add_handler(sock, accept_handler, IOLoop.READ) + return remove_handler + + +def is_valid_ip(ip: str) -> bool: + """Returns ``True`` if the given string is a well-formed IP address. + + Supports IPv4 and IPv6. + """ + if not ip or "\x00" in ip: + # getaddrinfo resolves empty strings to localhost, and truncates + # on zero bytes. + return False + try: + res = socket.getaddrinfo( + ip, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST + ) + return bool(res) + except socket.gaierror as e: + if e.args[0] == socket.EAI_NONAME: + return False + raise + return True + + +class Resolver(Configurable): + """Configurable asynchronous DNS resolver interface. + + By default, a blocking implementation is used (which simply calls + `socket.getaddrinfo`). An alternative implementation can be + chosen with the `Resolver.configure <.Configurable.configure>` + class method:: + + Resolver.configure('tornado.netutil.ThreadedResolver') + + The implementations of this interface included with Tornado are + + * `tornado.netutil.DefaultExecutorResolver` + * `tornado.netutil.BlockingResolver` (deprecated) + * `tornado.netutil.ThreadedResolver` (deprecated) + * `tornado.netutil.OverrideResolver` + * `tornado.platform.twisted.TwistedResolver` + * `tornado.platform.caresresolver.CaresResolver` + + .. versionchanged:: 5.0 + The default implementation has changed from `BlockingResolver` to + `DefaultExecutorResolver`. + """ + + @classmethod + def configurable_base(cls) -> Type["Resolver"]: + return Resolver + + @classmethod + def configurable_default(cls) -> Type["Resolver"]: + return DefaultExecutorResolver + + def resolve( + self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC + ) -> Awaitable[List[Tuple[int, Any]]]: + """Resolves an address. + + The ``host`` argument is a string which may be a hostname or a + literal IP address. + + Returns a `.Future` whose result is a list of (family, + address) pairs, where address is a tuple suitable to pass to + `socket.connect <socket.socket.connect>` (i.e. a ``(host, + port)`` pair for IPv4; additional fields may be present for + IPv6). If a ``callback`` is passed, it will be run with the + result as an argument when it is complete. + + :raises IOError: if the address cannot be resolved. + + .. versionchanged:: 4.4 + Standardized all implementations to raise `IOError`. + + .. versionchanged:: 6.0 The ``callback`` argument was removed. + Use the returned awaitable object instead. + + """ + raise NotImplementedError() + + def close(self) -> None: + """Closes the `Resolver`, freeing any resources used. + + .. versionadded:: 3.1 + + """ + pass + + +def _resolve_addr( + host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC +) -> List[Tuple[int, Any]]: + # On Solaris, getaddrinfo fails if the given port is not found + # in /etc/services and no socket type is given, so we must pass + # one here. The socket type used here doesn't seem to actually + # matter (we discard the one we get back in the results), + # so the addresses we return should still be usable with SOCK_DGRAM. + addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM) + results = [] + for fam, socktype, proto, canonname, address in addrinfo: + results.append((fam, address)) + return results + + +class DefaultExecutorResolver(Resolver): + """Resolver implementation using `.IOLoop.run_in_executor`. + + .. versionadded:: 5.0 + """ + + async def resolve( + self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC + ) -> List[Tuple[int, Any]]: + result = await IOLoop.current().run_in_executor( + None, _resolve_addr, host, port, family + ) + return result + + +class ExecutorResolver(Resolver): + """Resolver implementation using a `concurrent.futures.Executor`. + + Use this instead of `ThreadedResolver` when you require additional + control over the executor being used. + + The executor will be shut down when the resolver is closed unless + ``close_resolver=False``; use this if you want to reuse the same + executor elsewhere. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + + .. deprecated:: 5.0 + The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead + of this class. + """ + + def initialize( + self, executor: concurrent.futures.Executor = None, close_executor: bool = True + ) -> None: + self.io_loop = IOLoop.current() + if executor is not None: + self.executor = executor + self.close_executor = close_executor + else: + self.executor = dummy_executor + self.close_executor = False + + def close(self) -> None: + if self.close_executor: + self.executor.shutdown() + self.executor = None # type: ignore + + @run_on_executor + def resolve( + self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC + ) -> List[Tuple[int, Any]]: + return _resolve_addr(host, port, family) + + +class BlockingResolver(ExecutorResolver): + """Default `Resolver` implementation, using `socket.getaddrinfo`. + + The `.IOLoop` will be blocked during the resolution, although the + callback will not be run until the next `.IOLoop` iteration. + + .. deprecated:: 5.0 + The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead + of this class. + """ + + def initialize(self) -> None: # type: ignore + super(BlockingResolver, self).initialize() + + +class ThreadedResolver(ExecutorResolver): + """Multithreaded non-blocking `Resolver` implementation. + + Requires the `concurrent.futures` package to be installed + (available in the standard library since Python 3.2, + installable with ``pip install futures`` in older versions). + + The thread pool size can be configured with:: + + Resolver.configure('tornado.netutil.ThreadedResolver', + num_threads=10) + + .. versionchanged:: 3.1 + All ``ThreadedResolvers`` share a single thread pool, whose + size is set by the first one to be created. + + .. deprecated:: 5.0 + The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead + of this class. + """ + + _threadpool = None # type: ignore + _threadpool_pid = None # type: int + + def initialize(self, num_threads: int = 10) -> None: # type: ignore + threadpool = ThreadedResolver._create_threadpool(num_threads) + super(ThreadedResolver, self).initialize( + executor=threadpool, close_executor=False + ) + + @classmethod + def _create_threadpool( + cls, num_threads: int + ) -> concurrent.futures.ThreadPoolExecutor: + pid = os.getpid() + if cls._threadpool_pid != pid: + # Threads cannot survive after a fork, so if our pid isn't what it + # was when we created the pool then delete it. + cls._threadpool = None + if cls._threadpool is None: + cls._threadpool = concurrent.futures.ThreadPoolExecutor(num_threads) + cls._threadpool_pid = pid + return cls._threadpool + + +class OverrideResolver(Resolver): + """Wraps a resolver with a mapping of overrides. + + This can be used to make local DNS changes (e.g. for testing) + without modifying system-wide settings. + + The mapping can be in three formats:: + + { + # Hostname to host or ip + "example.com": "127.0.1.1", + + # Host+port to host+port + ("login.example.com", 443): ("localhost", 1443), + + # Host+port+address family to host+port + ("login.example.com", 443, socket.AF_INET6): ("::1", 1443), + } + + .. versionchanged:: 5.0 + Added support for host-port-family triplets. + """ + + def initialize(self, resolver: Resolver, mapping: dict) -> None: + self.resolver = resolver + self.mapping = mapping + + def close(self) -> None: + self.resolver.close() + + def resolve( + self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC + ) -> Awaitable[List[Tuple[int, Any]]]: + if (host, port, family) in self.mapping: + host, port = self.mapping[(host, port, family)] + elif (host, port) in self.mapping: + host, port = self.mapping[(host, port)] + elif host in self.mapping: + host = self.mapping[host] + return self.resolver.resolve(host, port, family) + + +# These are the keyword arguments to ssl.wrap_socket that must be translated +# to their SSLContext equivalents (the other arguments are still passed +# to SSLContext.wrap_socket). +_SSL_CONTEXT_KEYWORDS = frozenset( + ["ssl_version", "certfile", "keyfile", "cert_reqs", "ca_certs", "ciphers"] +) + + +def ssl_options_to_context( + ssl_options: Union[Dict[str, Any], ssl.SSLContext] +) -> ssl.SSLContext: + """Try to convert an ``ssl_options`` dictionary to an + `~ssl.SSLContext` object. + + The ``ssl_options`` dictionary contains keywords to be passed to + `ssl.wrap_socket`. In Python 2.7.9+, `ssl.SSLContext` objects can + be used instead. This function converts the dict form to its + `~ssl.SSLContext` equivalent, and may be used when a component which + accepts both forms needs to upgrade to the `~ssl.SSLContext` version + to use features like SNI or NPN. + """ + if isinstance(ssl_options, ssl.SSLContext): + return ssl_options + assert isinstance(ssl_options, dict) + assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options + # Can't use create_default_context since this interface doesn't + # tell us client vs server. + context = ssl.SSLContext(ssl_options.get("ssl_version", ssl.PROTOCOL_SSLv23)) + if "certfile" in ssl_options: + context.load_cert_chain( + ssl_options["certfile"], ssl_options.get("keyfile", None) + ) + if "cert_reqs" in ssl_options: + context.verify_mode = ssl_options["cert_reqs"] + if "ca_certs" in ssl_options: + context.load_verify_locations(ssl_options["ca_certs"]) + if "ciphers" in ssl_options: + context.set_ciphers(ssl_options["ciphers"]) + if hasattr(ssl, "OP_NO_COMPRESSION"): + # Disable TLS compression to avoid CRIME and related attacks. + # This constant depends on openssl version 1.0. + # TODO: Do we need to do this ourselves or can we trust + # the defaults? + context.options |= ssl.OP_NO_COMPRESSION + return context + + +def ssl_wrap_socket( + socket: socket.socket, + ssl_options: Union[Dict[str, Any], ssl.SSLContext], + server_hostname: str = None, + **kwargs: Any +) -> ssl.SSLSocket: + """Returns an ``ssl.SSLSocket`` wrapping the given socket. + + ``ssl_options`` may be either an `ssl.SSLContext` object or a + dictionary (as accepted by `ssl_options_to_context`). Additional + keyword arguments are passed to ``wrap_socket`` (either the + `~ssl.SSLContext` method or the `ssl` module function as + appropriate). + """ + context = ssl_options_to_context(ssl_options) + if ssl.HAS_SNI: + # In python 3.4, wrap_socket only accepts the server_hostname + # argument if HAS_SNI is true. + # TODO: add a unittest (python added server-side SNI support in 3.4) + # In the meantime it can be manually tested with + # python3 -m tornado.httpclient https://sni.velox.ch + return context.wrap_socket(socket, server_hostname=server_hostname, **kwargs) + else: + return context.wrap_socket(socket, **kwargs) diff --git a/venv/lib/python3.7/site-packages/tornado/options.py b/venv/lib/python3.7/site-packages/tornado/options.py new file mode 100644 index 0000000..4449ffa --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/options.py @@ -0,0 +1,726 @@ +# +# Copyright 2009 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. + +"""A command line parsing module that lets modules define their own options. + +This module is inspired by Google's `gflags +<https://github.com/google/python-gflags>`_. The primary difference +with libraries such as `argparse` is that a global registry is used so +that options may be defined in any module (it also enables +`tornado.log` by default). The rest of Tornado does not depend on this +module, so feel free to use `argparse` or other configuration +libraries if you prefer them. + +Options must be defined with `tornado.options.define` before use, +generally at the top level of a module. The options are then +accessible as attributes of `tornado.options.options`:: + + # myapp/db.py + from tornado.options import define, options + + define("mysql_host", default="127.0.0.1:3306", help="Main user DB") + define("memcache_hosts", default="127.0.0.1:11011", multiple=True, + help="Main user memcache servers") + + def connect(): + db = database.Connection(options.mysql_host) + ... + + # myapp/server.py + from tornado.options import define, options + + define("port", default=8080, help="port to listen on") + + def start_server(): + app = make_app() + app.listen(options.port) + +The ``main()`` method of your application does not need to be aware of all of +the options used throughout your program; they are all automatically loaded +when the modules are loaded. However, all modules that define options +must have been imported before the command line is parsed. + +Your ``main()`` method can parse the command line or parse a config file with +either `parse_command_line` or `parse_config_file`:: + + import myapp.db, myapp.server + import tornado.options + + if __name__ == '__main__': + tornado.options.parse_command_line() + # or + tornado.options.parse_config_file("/etc/server.conf") + +.. note:: + + When using multiple ``parse_*`` functions, pass ``final=False`` to all + but the last one, or side effects may occur twice (in particular, + this can result in log messages being doubled). + +`tornado.options.options` is a singleton instance of `OptionParser`, and +the top-level functions in this module (`define`, `parse_command_line`, etc) +simply call methods on it. You may create additional `OptionParser` +instances to define isolated sets of options, such as for subcommands. + +.. note:: + + By default, several options are defined that will configure the + standard `logging` module when `parse_command_line` or `parse_config_file` + are called. If you want Tornado to leave the logging configuration + alone so you can manage it yourself, either pass ``--logging=none`` + on the command line or do the following to disable it in code:: + + from tornado.options import options, parse_command_line + options.logging = None + parse_command_line() + +.. versionchanged:: 4.3 + Dashes and underscores are fully interchangeable in option names; + options can be defined, set, and read with any mix of the two. + Dashes are typical for command-line usage while config files require + underscores. +""" + +import datetime +import numbers +import re +import sys +import os +import textwrap + +from tornado.escape import _unicode, native_str +from tornado.log import define_logging_options +from tornado.util import basestring_type, exec_in + +import typing +from typing import Any, Iterator, Iterable, Tuple, Set, Dict, Callable, List, TextIO + +if typing.TYPE_CHECKING: + from typing import Optional # noqa: F401 + + +class Error(Exception): + """Exception raised by errors in the options module.""" + + pass + + +class OptionParser(object): + """A collection of options, a dictionary with object-like access. + + Normally accessed via static functions in the `tornado.options` module, + which reference a global instance. + """ + + def __init__(self) -> None: + # we have to use self.__dict__ because we override setattr. + self.__dict__["_options"] = {} + self.__dict__["_parse_callbacks"] = [] + self.define( + "help", + type=bool, + help="show this help information", + callback=self._help_callback, + ) + + def _normalize_name(self, name: str) -> str: + return name.replace("_", "-") + + def __getattr__(self, name: str) -> Any: + name = self._normalize_name(name) + if isinstance(self._options.get(name), _Option): + return self._options[name].value() + raise AttributeError("Unrecognized option %r" % name) + + def __setattr__(self, name: str, value: Any) -> None: + name = self._normalize_name(name) + if isinstance(self._options.get(name), _Option): + return self._options[name].set(value) + raise AttributeError("Unrecognized option %r" % name) + + def __iter__(self) -> Iterator: + return (opt.name for opt in self._options.values()) + + def __contains__(self, name: str) -> bool: + name = self._normalize_name(name) + return name in self._options + + def __getitem__(self, name: str) -> Any: + return self.__getattr__(name) + + def __setitem__(self, name: str, value: Any) -> None: + return self.__setattr__(name, value) + + def items(self) -> Iterable[Tuple[str, Any]]: + """An iterable of (name, value) pairs. + + .. versionadded:: 3.1 + """ + return [(opt.name, opt.value()) for name, opt in self._options.items()] + + def groups(self) -> Set[str]: + """The set of option-groups created by ``define``. + + .. versionadded:: 3.1 + """ + return set(opt.group_name for opt in self._options.values()) + + def group_dict(self, group: str) -> Dict[str, Any]: + """The names and values of options in a group. + + Useful for copying options into Application settings:: + + from tornado.options import define, parse_command_line, options + + define('template_path', group='application') + define('static_path', group='application') + + parse_command_line() + + application = Application( + handlers, **options.group_dict('application')) + + .. versionadded:: 3.1 + """ + return dict( + (opt.name, opt.value()) + for name, opt in self._options.items() + if not group or group == opt.group_name + ) + + def as_dict(self) -> Dict[str, Any]: + """The names and values of all options. + + .. versionadded:: 3.1 + """ + return dict((opt.name, opt.value()) for name, opt in self._options.items()) + + def define( + self, + name: str, + default: Any = None, + type: type = None, + help: str = None, + metavar: str = None, + multiple: bool = False, + group: str = None, + callback: Callable[[Any], None] = None, + ) -> None: + """Defines a new command line option. + + ``type`` can be any of `str`, `int`, `float`, `bool`, + `~datetime.datetime`, or `~datetime.timedelta`. If no ``type`` + is given but a ``default`` is, ``type`` is the type of + ``default``. Otherwise, ``type`` defaults to `str`. + + If ``multiple`` is True, the option value is a list of ``type`` + instead of an instance of ``type``. + + ``help`` and ``metavar`` are used to construct the + automatically generated command line help string. The help + message is formatted like:: + + --name=METAVAR help string + + ``group`` is used to group the defined options in logical + groups. By default, command line options are grouped by the + file in which they are defined. + + Command line option names must be unique globally. + + If a ``callback`` is given, it will be run with the new value whenever + the option is changed. This can be used to combine command-line + and file-based options:: + + define("config", type=str, help="path to config file", + callback=lambda path: parse_config_file(path, final=False)) + + With this definition, options in the file specified by ``--config`` will + override options set earlier on the command line, but can be overridden + by later flags. + + """ + normalized = self._normalize_name(name) + if normalized in self._options: + raise Error( + "Option %r already defined in %s" + % (normalized, self._options[normalized].file_name) + ) + frame = sys._getframe(0) + options_file = frame.f_code.co_filename + + # Can be called directly, or through top level define() fn, in which + # case, step up above that frame to look for real caller. + if ( + frame.f_back.f_code.co_filename == options_file + and frame.f_back.f_code.co_name == "define" + ): + frame = frame.f_back + + file_name = frame.f_back.f_code.co_filename + if file_name == options_file: + file_name = "" + if type is None: + if not multiple and default is not None: + type = default.__class__ + else: + type = str + if group: + group_name = group # type: Optional[str] + else: + group_name = file_name + option = _Option( + name, + file_name=file_name, + default=default, + type=type, + help=help, + metavar=metavar, + multiple=multiple, + group_name=group_name, + callback=callback, + ) + self._options[normalized] = option + + def parse_command_line( + self, args: List[str] = None, final: bool = True + ) -> List[str]: + """Parses all options given on the command line (defaults to + `sys.argv`). + + Options look like ``--option=value`` and are parsed according + to their ``type``. For boolean options, ``--option`` is + equivalent to ``--option=true`` + + If the option has ``multiple=True``, comma-separated values + are accepted. For multi-value integer options, the syntax + ``x:y`` is also accepted and equivalent to ``range(x, y)``. + + Note that ``args[0]`` is ignored since it is the program name + in `sys.argv`. + + We return a list of all arguments that are not parsed as options. + + If ``final`` is ``False``, parse callbacks will not be run. + This is useful for applications that wish to combine configurations + from multiple sources. + + """ + if args is None: + args = sys.argv + remaining = [] # type: List[str] + for i in range(1, len(args)): + # All things after the last option are command line arguments + if not args[i].startswith("-"): + remaining = args[i:] + break + if args[i] == "--": + remaining = args[i + 1 :] + break + arg = args[i].lstrip("-") + name, equals, value = arg.partition("=") + name = self._normalize_name(name) + if name not in self._options: + self.print_help() + raise Error("Unrecognized command line option: %r" % name) + option = self._options[name] + if not equals: + if option.type == bool: + value = "true" + else: + raise Error("Option %r requires a value" % name) + option.parse(value) + + if final: + self.run_parse_callbacks() + + return remaining + + def parse_config_file(self, path: str, final: bool = True) -> None: + """Parses and loads the config file at the given path. + + The config file contains Python code that will be executed (so + it is **not safe** to use untrusted config files). Anything in + the global namespace that matches a defined option will be + used to set that option's value. + + Options may either be the specified type for the option or + strings (in which case they will be parsed the same way as in + `.parse_command_line`) + + Example (using the options defined in the top-level docs of + this module):: + + port = 80 + mysql_host = 'mydb.example.com:3306' + # Both lists and comma-separated strings are allowed for + # multiple=True. + memcache_hosts = ['cache1.example.com:11011', + 'cache2.example.com:11011'] + memcache_hosts = 'cache1.example.com:11011,cache2.example.com:11011' + + If ``final`` is ``False``, parse callbacks will not be run. + This is useful for applications that wish to combine configurations + from multiple sources. + + .. note:: + + `tornado.options` is primarily a command-line library. + Config file support is provided for applications that wish + to use it, but applications that prefer config files may + wish to look at other libraries instead. + + .. versionchanged:: 4.1 + Config files are now always interpreted as utf-8 instead of + the system default encoding. + + .. versionchanged:: 4.4 + The special variable ``__file__`` is available inside config + files, specifying the absolute path to the config file itself. + + .. versionchanged:: 5.1 + Added the ability to set options via strings in config files. + + """ + config = {"__file__": os.path.abspath(path)} + with open(path, "rb") as f: + exec_in(native_str(f.read()), config, config) + for name in config: + normalized = self._normalize_name(name) + if normalized in self._options: + option = self._options[normalized] + if option.multiple: + if not isinstance(config[name], (list, str)): + raise Error( + "Option %r is required to be a list of %s " + "or a comma-separated string" + % (option.name, option.type.__name__) + ) + + if type(config[name]) == str and option.type != str: + option.parse(config[name]) + else: + option.set(config[name]) + + if final: + self.run_parse_callbacks() + + def print_help(self, file: TextIO = None) -> None: + """Prints all the command line options to stderr (or another file).""" + if file is None: + file = sys.stderr + print("Usage: %s [OPTIONS]" % sys.argv[0], file=file) + print("\nOptions:\n", file=file) + by_group = {} # type: Dict[str, List[_Option]] + for option in self._options.values(): + by_group.setdefault(option.group_name, []).append(option) + + for filename, o in sorted(by_group.items()): + if filename: + print("\n%s options:\n" % os.path.normpath(filename), file=file) + o.sort(key=lambda option: option.name) + for option in o: + # Always print names with dashes in a CLI context. + prefix = self._normalize_name(option.name) + if option.metavar: + prefix += "=" + option.metavar + description = option.help or "" + if option.default is not None and option.default != "": + description += " (default %s)" % option.default + lines = textwrap.wrap(description, 79 - 35) + if len(prefix) > 30 or len(lines) == 0: + lines.insert(0, "") + print(" --%-30s %s" % (prefix, lines[0]), file=file) + for line in lines[1:]: + print("%-34s %s" % (" ", line), file=file) + print(file=file) + + def _help_callback(self, value: bool) -> None: + if value: + self.print_help() + sys.exit(0) + + def add_parse_callback(self, callback: Callable[[], None]) -> None: + """Adds a parse callback, to be invoked when option parsing is done.""" + self._parse_callbacks.append(callback) + + def run_parse_callbacks(self) -> None: + for callback in self._parse_callbacks: + callback() + + def mockable(self) -> "_Mockable": + """Returns a wrapper around self that is compatible with + `mock.patch <unittest.mock.patch>`. + + The `mock.patch <unittest.mock.patch>` function (included in + the standard library `unittest.mock` package since Python 3.3, + or in the third-party ``mock`` package for older versions of + Python) is incompatible with objects like ``options`` that + override ``__getattr__`` and ``__setattr__``. This function + returns an object that can be used with `mock.patch.object + <unittest.mock.patch.object>` to modify option values:: + + with mock.patch.object(options.mockable(), 'name', value): + assert options.name == value + """ + return _Mockable(self) + + +class _Mockable(object): + """`mock.patch` compatible wrapper for `OptionParser`. + + As of ``mock`` version 1.0.1, when an object uses ``__getattr__`` + hooks instead of ``__dict__``, ``patch.__exit__`` tries to delete + the attribute it set instead of setting a new one (assuming that + the object does not catpure ``__setattr__``, so the patch + created a new attribute in ``__dict__``). + + _Mockable's getattr and setattr pass through to the underlying + OptionParser, and delattr undoes the effect of a previous setattr. + """ + + def __init__(self, options: OptionParser) -> None: + # Modify __dict__ directly to bypass __setattr__ + self.__dict__["_options"] = options + self.__dict__["_originals"] = {} + + def __getattr__(self, name: str) -> Any: + return getattr(self._options, name) + + def __setattr__(self, name: str, value: Any) -> None: + assert name not in self._originals, "don't reuse mockable objects" + self._originals[name] = getattr(self._options, name) + setattr(self._options, name, value) + + def __delattr__(self, name: str) -> None: + setattr(self._options, name, self._originals.pop(name)) + + +class _Option(object): + # This class could almost be made generic, but the way the types + # interact with the multiple argument makes this tricky. (default + # and the callback use List[T], but type is still Type[T]). + UNSET = object() + + def __init__( + self, + name: str, + default: Any = None, + type: type = None, + help: str = None, + metavar: str = None, + multiple: bool = False, + file_name: str = None, + group_name: str = None, + callback: Callable[[Any], None] = None, + ) -> None: + if default is None and multiple: + default = [] + self.name = name + if type is None: + raise ValueError("type must not be None") + self.type = type + self.help = help + self.metavar = metavar + self.multiple = multiple + self.file_name = file_name + self.group_name = group_name + self.callback = callback + self.default = default + self._value = _Option.UNSET # type: Any + + def value(self) -> Any: + return self.default if self._value is _Option.UNSET else self._value + + def parse(self, value: str) -> Any: + _parse = { + datetime.datetime: self._parse_datetime, + datetime.timedelta: self._parse_timedelta, + bool: self._parse_bool, + basestring_type: self._parse_string, + }.get( + self.type, self.type + ) # type: Callable[[str], Any] + if self.multiple: + self._value = [] + for part in value.split(","): + if issubclass(self.type, numbers.Integral): + # allow ranges of the form X:Y (inclusive at both ends) + lo_str, _, hi_str = part.partition(":") + lo = _parse(lo_str) + hi = _parse(hi_str) if hi_str else lo + self._value.extend(range(lo, hi + 1)) + else: + self._value.append(_parse(part)) + else: + self._value = _parse(value) + if self.callback is not None: + self.callback(self._value) + return self.value() + + def set(self, value: Any) -> None: + if self.multiple: + if not isinstance(value, list): + raise Error( + "Option %r is required to be a list of %s" + % (self.name, self.type.__name__) + ) + for item in value: + if item is not None and not isinstance(item, self.type): + raise Error( + "Option %r is required to be a list of %s" + % (self.name, self.type.__name__) + ) + else: + if value is not None and not isinstance(value, self.type): + raise Error( + "Option %r is required to be a %s (%s given)" + % (self.name, self.type.__name__, type(value)) + ) + self._value = value + if self.callback is not None: + self.callback(self._value) + + # Supported date/time formats in our options + _DATETIME_FORMATS = [ + "%a %b %d %H:%M:%S %Y", + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M", + "%Y-%m-%dT%H:%M", + "%Y%m%d %H:%M:%S", + "%Y%m%d %H:%M", + "%Y-%m-%d", + "%Y%m%d", + "%H:%M:%S", + "%H:%M", + ] + + def _parse_datetime(self, value: str) -> datetime.datetime: + for format in self._DATETIME_FORMATS: + try: + return datetime.datetime.strptime(value, format) + except ValueError: + pass + raise Error("Unrecognized date/time format: %r" % value) + + _TIMEDELTA_ABBREV_DICT = { + "h": "hours", + "m": "minutes", + "min": "minutes", + "s": "seconds", + "sec": "seconds", + "ms": "milliseconds", + "us": "microseconds", + "d": "days", + "w": "weeks", + } + + _FLOAT_PATTERN = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?" + + _TIMEDELTA_PATTERN = re.compile( + r"\s*(%s)\s*(\w*)\s*" % _FLOAT_PATTERN, re.IGNORECASE + ) + + def _parse_timedelta(self, value: str) -> datetime.timedelta: + try: + sum = datetime.timedelta() + start = 0 + while start < len(value): + m = self._TIMEDELTA_PATTERN.match(value, start) + if not m: + raise Exception() + num = float(m.group(1)) + units = m.group(2) or "seconds" + units = self._TIMEDELTA_ABBREV_DICT.get(units, units) + sum += datetime.timedelta(**{units: num}) + start = m.end() + return sum + except Exception: + raise + + def _parse_bool(self, value: str) -> bool: + return value.lower() not in ("false", "0", "f") + + def _parse_string(self, value: str) -> str: + return _unicode(value) + + +options = OptionParser() +"""Global options object. + +All defined options are available as attributes on this object. +""" + + +def define( + name: str, + default: Any = None, + type: type = None, + help: str = None, + metavar: str = None, + multiple: bool = False, + group: str = None, + callback: Callable[[Any], None] = None, +) -> None: + """Defines an option in the global namespace. + + See `OptionParser.define`. + """ + return options.define( + name, + default=default, + type=type, + help=help, + metavar=metavar, + multiple=multiple, + group=group, + callback=callback, + ) + + +def parse_command_line(args: List[str] = None, final: bool = True) -> List[str]: + """Parses global options from the command line. + + See `OptionParser.parse_command_line`. + """ + return options.parse_command_line(args, final=final) + + +def parse_config_file(path: str, final: bool = True) -> None: + """Parses global options from a config file. + + See `OptionParser.parse_config_file`. + """ + return options.parse_config_file(path, final=final) + + +def print_help(file: TextIO = None) -> None: + """Prints all the command line options to stderr (or another file). + + See `OptionParser.print_help`. + """ + return options.print_help(file) + + +def add_parse_callback(callback: Callable[[], None]) -> None: + """Adds a parse callback, to be invoked when option parsing is done. + + See `OptionParser.add_parse_callback` + """ + options.add_parse_callback(callback) + + +# Default options +define_logging_options(options) diff --git a/venv/lib/python3.7/site-packages/tornado/platform/__init__.py b/venv/lib/python3.7/site-packages/tornado/platform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/tornado/platform/asyncio.py b/venv/lib/python3.7/site-packages/tornado/platform/asyncio.py new file mode 100644 index 0000000..0b79f82 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/asyncio.py @@ -0,0 +1,337 @@ +"""Bridges between the `asyncio` module and Tornado IOLoop. + +.. versionadded:: 3.2 + +This module integrates Tornado with the ``asyncio`` module introduced +in Python 3.4. This makes it possible to combine the two libraries on +the same event loop. + +.. deprecated:: 5.0 + + While the code in this module is still used, it is now enabled + automatically when `asyncio` is available, so applications should + no longer need to refer to this module directly. + +.. note:: + + Tornado requires the `~asyncio.AbstractEventLoop.add_reader` family of + methods, so it is not compatible with the `~asyncio.ProactorEventLoop` on + Windows. Use the `~asyncio.SelectorEventLoop` instead. +""" + +import concurrent.futures +import functools + +from threading import get_ident +from tornado.gen import convert_yielded +from tornado.ioloop import IOLoop, _Selectable + +import asyncio + +import typing +from typing import Any, TypeVar, Awaitable, Callable, Union, Optional + +if typing.TYPE_CHECKING: + from typing import Set, Dict, Tuple # noqa: F401 + +_T = TypeVar("_T") + + +class BaseAsyncIOLoop(IOLoop): + def initialize( # type: ignore + self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any + ) -> None: + self.asyncio_loop = asyncio_loop + # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler) + self.handlers = {} # type: Dict[int, Tuple[Union[int, _Selectable], Callable]] + # Set of fds listening for reads/writes + self.readers = set() # type: Set[int] + self.writers = set() # type: Set[int] + self.closing = False + # If an asyncio loop was closed through an asyncio interface + # instead of IOLoop.close(), we'd never hear about it and may + # have left a dangling reference in our map. In case an + # application (or, more likely, a test suite) creates and + # destroys a lot of event loops in this way, check here to + # ensure that we don't have a lot of dead loops building up in + # the map. + # + # TODO(bdarnell): consider making self.asyncio_loop a weakref + # for AsyncIOMainLoop and make _ioloop_for_asyncio a + # WeakKeyDictionary. + for loop in list(IOLoop._ioloop_for_asyncio): + if loop.is_closed(): + del IOLoop._ioloop_for_asyncio[loop] + IOLoop._ioloop_for_asyncio[asyncio_loop] = self + + self._thread_identity = 0 + + super(BaseAsyncIOLoop, self).initialize(**kwargs) + + def assign_thread_identity() -> None: + self._thread_identity = get_ident() + + self.add_callback(assign_thread_identity) + + def close(self, all_fds: bool = False) -> None: + self.closing = True + for fd in list(self.handlers): + fileobj, handler_func = self.handlers[fd] + self.remove_handler(fd) + if all_fds: + self.close_fd(fileobj) + # Remove the mapping before closing the asyncio loop. If this + # happened in the other order, we could race against another + # initialize() call which would see the closed asyncio loop, + # assume it was closed from the asyncio side, and do this + # cleanup for us, leading to a KeyError. + del IOLoop._ioloop_for_asyncio[self.asyncio_loop] + self.asyncio_loop.close() + + def add_handler( + self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int + ) -> None: + fd, fileobj = self.split_fd(fd) + if fd in self.handlers: + raise ValueError("fd %s added twice" % fd) + self.handlers[fd] = (fileobj, handler) + if events & IOLoop.READ: + self.asyncio_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ) + self.readers.add(fd) + if events & IOLoop.WRITE: + self.asyncio_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE) + self.writers.add(fd) + + def update_handler(self, fd: Union[int, _Selectable], events: int) -> None: + fd, fileobj = self.split_fd(fd) + if events & IOLoop.READ: + if fd not in self.readers: + self.asyncio_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ) + self.readers.add(fd) + else: + if fd in self.readers: + self.asyncio_loop.remove_reader(fd) + self.readers.remove(fd) + if events & IOLoop.WRITE: + if fd not in self.writers: + self.asyncio_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE) + self.writers.add(fd) + else: + if fd in self.writers: + self.asyncio_loop.remove_writer(fd) + self.writers.remove(fd) + + def remove_handler(self, fd: Union[int, _Selectable]) -> None: + fd, fileobj = self.split_fd(fd) + if fd not in self.handlers: + return + if fd in self.readers: + self.asyncio_loop.remove_reader(fd) + self.readers.remove(fd) + if fd in self.writers: + self.asyncio_loop.remove_writer(fd) + self.writers.remove(fd) + del self.handlers[fd] + + def _handle_events(self, fd: int, events: int) -> None: + fileobj, handler_func = self.handlers[fd] + handler_func(fileobj, events) + + def start(self) -> None: + try: + old_loop = asyncio.get_event_loop() + except (RuntimeError, AssertionError): + old_loop = None # type: ignore + try: + self._setup_logging() + asyncio.set_event_loop(self.asyncio_loop) + self.asyncio_loop.run_forever() + finally: + asyncio.set_event_loop(old_loop) + + def stop(self) -> None: + self.asyncio_loop.stop() + + def call_at( + self, when: float, callback: Callable[..., None], *args: Any, **kwargs: Any + ) -> object: + # asyncio.call_at supports *args but not **kwargs, so bind them here. + # We do not synchronize self.time and asyncio_loop.time, so + # convert from absolute to relative. + return self.asyncio_loop.call_later( + max(0, when - self.time()), + self._run_callback, + functools.partial(callback, *args, **kwargs), + ) + + def remove_timeout(self, timeout: object) -> None: + timeout.cancel() # type: ignore + + def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: + if get_ident() == self._thread_identity: + call_soon = self.asyncio_loop.call_soon + else: + call_soon = self.asyncio_loop.call_soon_threadsafe + try: + call_soon(self._run_callback, functools.partial(callback, *args, **kwargs)) + except RuntimeError: + # "Event loop is closed". Swallow the exception for + # consistency with PollIOLoop (and logical consistency + # with the fact that we can't guarantee that an + # add_callback that completes without error will + # eventually execute). + pass + + def add_callback_from_signal( + self, callback: Callable, *args: Any, **kwargs: Any + ) -> None: + try: + self.asyncio_loop.call_soon_threadsafe( + self._run_callback, functools.partial(callback, *args, **kwargs) + ) + except RuntimeError: + pass + + def run_in_executor( + self, + executor: Optional[concurrent.futures.Executor], + func: Callable[..., _T], + *args: Any + ) -> Awaitable[_T]: + return self.asyncio_loop.run_in_executor(executor, func, *args) + + def set_default_executor(self, executor: concurrent.futures.Executor) -> None: + return self.asyncio_loop.set_default_executor(executor) + + +class AsyncIOMainLoop(BaseAsyncIOLoop): + """``AsyncIOMainLoop`` creates an `.IOLoop` that corresponds to the + current ``asyncio`` event loop (i.e. the one returned by + ``asyncio.get_event_loop()``). + + .. deprecated:: 5.0 + + Now used automatically when appropriate; it is no longer necessary + to refer to this class directly. + + .. versionchanged:: 5.0 + + Closing an `AsyncIOMainLoop` now closes the underlying asyncio loop. + """ + + def initialize(self, **kwargs: Any) -> None: # type: ignore + super(AsyncIOMainLoop, self).initialize(asyncio.get_event_loop(), **kwargs) + + def make_current(self) -> None: + # AsyncIOMainLoop already refers to the current asyncio loop so + # nothing to do here. + pass + + +class AsyncIOLoop(BaseAsyncIOLoop): + """``AsyncIOLoop`` is an `.IOLoop` that runs on an ``asyncio`` event loop. + This class follows the usual Tornado semantics for creating new + ``IOLoops``; these loops are not necessarily related to the + ``asyncio`` default event loop. + + Each ``AsyncIOLoop`` creates a new ``asyncio.EventLoop``; this object + can be accessed with the ``asyncio_loop`` attribute. + + .. versionchanged:: 5.0 + + When an ``AsyncIOLoop`` becomes the current `.IOLoop`, it also sets + the current `asyncio` event loop. + + .. deprecated:: 5.0 + + Now used automatically when appropriate; it is no longer necessary + to refer to this class directly. + """ + + def initialize(self, **kwargs: Any) -> None: # type: ignore + self.is_current = False + loop = asyncio.new_event_loop() + try: + super(AsyncIOLoop, self).initialize(loop, **kwargs) + except Exception: + # If initialize() does not succeed (taking ownership of the loop), + # we have to close it. + loop.close() + raise + + def close(self, all_fds: bool = False) -> None: + if self.is_current: + self.clear_current() + super(AsyncIOLoop, self).close(all_fds=all_fds) + + def make_current(self) -> None: + if not self.is_current: + try: + self.old_asyncio = asyncio.get_event_loop() + except (RuntimeError, AssertionError): + self.old_asyncio = None # type: ignore + self.is_current = True + asyncio.set_event_loop(self.asyncio_loop) + + def _clear_current_hook(self) -> None: + if self.is_current: + asyncio.set_event_loop(self.old_asyncio) + self.is_current = False + + +def to_tornado_future(asyncio_future: asyncio.Future) -> asyncio.Future: + """Convert an `asyncio.Future` to a `tornado.concurrent.Future`. + + .. versionadded:: 4.1 + + .. deprecated:: 5.0 + Tornado ``Futures`` have been merged with `asyncio.Future`, + so this method is now a no-op. + """ + return asyncio_future + + +def to_asyncio_future(tornado_future: asyncio.Future) -> asyncio.Future: + """Convert a Tornado yieldable object to an `asyncio.Future`. + + .. versionadded:: 4.1 + + .. versionchanged:: 4.3 + Now accepts any yieldable object, not just + `tornado.concurrent.Future`. + + .. deprecated:: 5.0 + Tornado ``Futures`` have been merged with `asyncio.Future`, + so this method is now equivalent to `tornado.gen.convert_yielded`. + """ + return convert_yielded(tornado_future) + + +class AnyThreadEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore + """Event loop policy that allows loop creation on any thread. + + The default `asyncio` event loop policy only automatically creates + event loops in the main threads. Other threads must create event + loops explicitly or `asyncio.get_event_loop` (and therefore + `.IOLoop.current`) will fail. Installing this policy allows event + loops to be created automatically on any thread, matching the + behavior of Tornado versions prior to 5.0 (or 5.0 on Python 2). + + Usage:: + + asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) + + .. versionadded:: 5.0 + + """ + + def get_event_loop(self) -> asyncio.AbstractEventLoop: + try: + return super().get_event_loop() + except (RuntimeError, AssertionError): + # This was an AssertionError in python 3.4.2 (which ships with debian jessie) + # and changed to a RuntimeError in 3.4.3. + # "There is no current event loop in thread %r" + loop = self.new_event_loop() + self.set_event_loop(loop) + return loop diff --git a/venv/lib/python3.7/site-packages/tornado/platform/auto.py b/venv/lib/python3.7/site-packages/tornado/platform/auto.py new file mode 100644 index 0000000..4f1b6ac --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/auto.py @@ -0,0 +1,32 @@ +# +# Copyright 2011 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. + +"""Implementation of platform-specific functionality. + +For each function or class described in `tornado.platform.interface`, +the appropriate platform-specific implementation exists in this module. +Most code that needs access to this functionality should do e.g.:: + + from tornado.platform.auto import set_close_exec +""" + +import os + +if os.name == "nt": + from tornado.platform.windows import set_close_exec +else: + from tornado.platform.posix import set_close_exec + +__all__ = ["set_close_exec"] diff --git a/venv/lib/python3.7/site-packages/tornado/platform/caresresolver.py b/venv/lib/python3.7/site-packages/tornado/platform/caresresolver.py new file mode 100644 index 0000000..e2c5009 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/caresresolver.py @@ -0,0 +1,89 @@ +import pycares # type: ignore +import socket + +from tornado.concurrent import Future +from tornado import gen +from tornado.ioloop import IOLoop +from tornado.netutil import Resolver, is_valid_ip + +import typing + +if typing.TYPE_CHECKING: + from typing import Generator, Any, List, Tuple, Dict # noqa: F401 + + +class CaresResolver(Resolver): + """Name resolver based on the c-ares library. + + This is a non-blocking and non-threaded resolver. It may not produce + the same results as the system resolver, but can be used for non-blocking + resolution when threads cannot be used. + + c-ares fails to resolve some names when ``family`` is ``AF_UNSPEC``, + so it is only recommended for use in ``AF_INET`` (i.e. IPv4). This is + the default for ``tornado.simple_httpclient``, but other libraries + may default to ``AF_UNSPEC``. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + """ + + def initialize(self) -> None: + self.io_loop = IOLoop.current() + self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb) + self.fds = {} # type: Dict[int, int] + + def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None: + state = (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0) + if not state: + self.io_loop.remove_handler(fd) + del self.fds[fd] + elif fd in self.fds: + self.io_loop.update_handler(fd, state) + self.fds[fd] = state + else: + self.io_loop.add_handler(fd, self._handle_events, state) + self.fds[fd] = state + + def _handle_events(self, fd: int, events: int) -> None: + read_fd = pycares.ARES_SOCKET_BAD + write_fd = pycares.ARES_SOCKET_BAD + if events & IOLoop.READ: + read_fd = fd + if events & IOLoop.WRITE: + write_fd = fd + self.channel.process_fd(read_fd, write_fd) + + @gen.coroutine + def resolve( + self, host: str, port: int, family: int = 0 + ) -> "Generator[Any, Any, List[Tuple[int, Any]]]": + if is_valid_ip(host): + addresses = [host] + else: + # gethostbyname doesn't take callback as a kwarg + fut = Future() # type: Future[Tuple[Any, Any]] + self.channel.gethostbyname( + host, family, lambda result, error: fut.set_result((result, error)) + ) + result, error = yield fut + if error: + raise IOError( + "C-Ares returned error %s: %s while resolving %s" + % (error, pycares.errno.strerror(error), host) + ) + addresses = result.addresses + addrinfo = [] + for address in addresses: + if "." in address: + address_family = socket.AF_INET + elif ":" in address: + address_family = socket.AF_INET6 + else: + address_family = socket.AF_UNSPEC + if family != socket.AF_UNSPEC and family != address_family: + raise IOError( + "Requested socket family %d but got %d" % (family, address_family) + ) + addrinfo.append((typing.cast(int, address_family), (address, port))) + return addrinfo diff --git a/venv/lib/python3.7/site-packages/tornado/platform/interface.py b/venv/lib/python3.7/site-packages/tornado/platform/interface.py new file mode 100644 index 0000000..11afdb1 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/interface.py @@ -0,0 +1,26 @@ +# +# Copyright 2011 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. + +"""Interfaces for platform-specific functionality. + +This module exists primarily for documentation purposes and as base classes +for other tornado.platform modules. Most code should import the appropriate +implementation from `tornado.platform.auto`. +""" + + +def set_close_exec(fd: int) -> None: + """Sets the close-on-exec bit (``FD_CLOEXEC``)for a file descriptor.""" + raise NotImplementedError() diff --git a/venv/lib/python3.7/site-packages/tornado/platform/posix.py b/venv/lib/python3.7/site-packages/tornado/platform/posix.py new file mode 100644 index 0000000..0ab27ca --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/posix.py @@ -0,0 +1,29 @@ +# +# Copyright 2011 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. + +"""Posix implementations of platform-specific functionality.""" + +import fcntl +import os + + +def set_close_exec(fd: int) -> None: + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) + + +def _set_nonblocking(fd: int) -> None: + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) diff --git a/venv/lib/python3.7/site-packages/tornado/platform/twisted.py b/venv/lib/python3.7/site-packages/tornado/platform/twisted.py new file mode 100644 index 0000000..3881631 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/twisted.py @@ -0,0 +1,131 @@ +# Author: Ovidiu Predescu +# Date: July 2011 +# +# 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. +"""Bridges between the Twisted reactor and Tornado IOLoop. + +This module lets you run applications and libraries written for +Twisted in a Tornado application. It can be used in two modes, +depending on which library's underlying event loop you want to use. + +This module has been tested with Twisted versions 11.0.0 and newer. +""" + +import socket +import sys + +import twisted.internet.abstract # type: ignore +import twisted.internet.asyncioreactor # type: ignore +from twisted.internet.defer import Deferred # type: ignore +from twisted.python import failure # type: ignore +import twisted.names.cache # type: ignore +import twisted.names.client # type: ignore +import twisted.names.hosts # type: ignore +import twisted.names.resolve # type: ignore + + +from tornado.concurrent import Future, future_set_exc_info +from tornado.escape import utf8 +from tornado import gen +from tornado.netutil import Resolver + +import typing + +if typing.TYPE_CHECKING: + from typing import Generator, Any, List, Tuple # noqa: F401 + + +class TwistedResolver(Resolver): + """Twisted-based asynchronous resolver. + + This is a non-blocking and non-threaded resolver. It is + recommended only when threads cannot be used, since it has + limitations compared to the standard ``getaddrinfo``-based + `~tornado.netutil.Resolver` and + `~tornado.netutil.DefaultExecutorResolver`. Specifically, it returns at + most one result, and arguments other than ``host`` and ``family`` + are ignored. It may fail to resolve when ``family`` is not + ``socket.AF_UNSPEC``. + + Requires Twisted 12.1 or newer. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + """ + + def initialize(self) -> None: + # partial copy of twisted.names.client.createResolver, which doesn't + # allow for a reactor to be passed in. + self.reactor = twisted.internet.asyncioreactor.AsyncioSelectorReactor() + + host_resolver = twisted.names.hosts.Resolver("/etc/hosts") + cache_resolver = twisted.names.cache.CacheResolver(reactor=self.reactor) + real_resolver = twisted.names.client.Resolver( + "/etc/resolv.conf", reactor=self.reactor + ) + self.resolver = twisted.names.resolve.ResolverChain( + [host_resolver, cache_resolver, real_resolver] + ) + + @gen.coroutine + def resolve( + self, host: str, port: int, family: int = 0 + ) -> "Generator[Any, Any, List[Tuple[int, Any]]]": + # getHostByName doesn't accept IP addresses, so if the input + # looks like an IP address just return it immediately. + if twisted.internet.abstract.isIPAddress(host): + resolved = host + resolved_family = socket.AF_INET + elif twisted.internet.abstract.isIPv6Address(host): + resolved = host + resolved_family = socket.AF_INET6 + else: + deferred = self.resolver.getHostByName(utf8(host)) + fut = Future() # type: Future[Any] + deferred.addBoth(fut.set_result) + resolved = yield fut + if isinstance(resolved, failure.Failure): + try: + resolved.raiseException() + except twisted.names.error.DomainError as e: + raise IOError(e) + elif twisted.internet.abstract.isIPAddress(resolved): + resolved_family = socket.AF_INET + elif twisted.internet.abstract.isIPv6Address(resolved): + resolved_family = socket.AF_INET6 + else: + resolved_family = socket.AF_UNSPEC + if family != socket.AF_UNSPEC and family != resolved_family: + raise Exception( + "Requested socket family %d but got %d" % (family, resolved_family) + ) + result = [(typing.cast(int, resolved_family), (resolved, port))] + return result + + +if hasattr(gen.convert_yielded, "register"): + + @gen.convert_yielded.register(Deferred) # type: ignore + def _(d: Deferred) -> Future: + f = Future() # type: Future[Any] + + def errback(failure: failure.Failure) -> None: + try: + failure.raiseException() + # Should never happen, but just in case + raise Exception("errback called without error") + except: + future_set_exc_info(f, sys.exc_info()) + + d.addCallbacks(f.set_result, errback) + return f diff --git a/venv/lib/python3.7/site-packages/tornado/platform/windows.py b/venv/lib/python3.7/site-packages/tornado/platform/windows.py new file mode 100644 index 0000000..ddbde20 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/platform/windows.py @@ -0,0 +1,22 @@ +# NOTE: win32 support is currently experimental, and not recommended +# for production use. + +import ctypes +import ctypes.wintypes + +# See: http://msdn.microsoft.com/en-us/library/ms724935(VS.85).aspx +SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation # type: ignore +SetHandleInformation.argtypes = ( + ctypes.wintypes.HANDLE, + ctypes.wintypes.DWORD, + ctypes.wintypes.DWORD, +) +SetHandleInformation.restype = ctypes.wintypes.BOOL + +HANDLE_FLAG_INHERIT = 0x00000001 + + +def set_close_exec(fd: int) -> None: + success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, 0) + if not success: + raise ctypes.WinError() # type: ignore diff --git a/venv/lib/python3.7/site-packages/tornado/process.py b/venv/lib/python3.7/site-packages/tornado/process.py new file mode 100644 index 0000000..c1a5fce --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/process.py @@ -0,0 +1,373 @@ +# +# Copyright 2011 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. + +"""Utilities for working with multiple processes, including both forking +the server into multiple processes and managing subprocesses. +""" + +import errno +import os +import multiprocessing +import signal +import subprocess +import sys +import time + +from binascii import hexlify + +from tornado.concurrent import ( + Future, + future_set_result_unless_cancelled, + future_set_exception_unless_cancelled, +) +from tornado import ioloop +from tornado.iostream import PipeIOStream +from tornado.log import gen_log +from tornado.platform.auto import set_close_exec +from tornado.util import errno_from_exception + +import typing +from typing import Tuple, Optional, Any, Callable + +if typing.TYPE_CHECKING: + from typing import List # noqa: F401 + +# Re-export this exception for convenience. +CalledProcessError = subprocess.CalledProcessError + + +def cpu_count() -> int: + """Returns the number of processors on this machine.""" + if multiprocessing is None: + return 1 + try: + return multiprocessing.cpu_count() + except NotImplementedError: + pass + try: + return os.sysconf("SC_NPROCESSORS_CONF") + except (AttributeError, ValueError): + pass + gen_log.error("Could not detect number of processors; assuming 1") + return 1 + + +def _reseed_random() -> None: + if "random" not in sys.modules: + return + import random + + # If os.urandom is available, this method does the same thing as + # random.seed (at least as of python 2.6). If os.urandom is not + # available, we mix in the pid in addition to a timestamp. + try: + seed = int(hexlify(os.urandom(16)), 16) + except NotImplementedError: + seed = int(time.time() * 1000) ^ os.getpid() + random.seed(seed) + + +def _pipe_cloexec() -> Tuple[int, int]: + r, w = os.pipe() + set_close_exec(r) + set_close_exec(w) + return r, w + + +_task_id = None + + +def fork_processes(num_processes: Optional[int], max_restarts: int = None) -> int: + """Starts multiple worker processes. + + If ``num_processes`` is None or <= 0, we detect the number of cores + available on this machine and fork that number of child + processes. If ``num_processes`` is given and > 0, we fork that + specific number of sub-processes. + + Since we use processes and not threads, there is no shared memory + between any server code. + + Note that multiple processes are not compatible with the autoreload + module (or the ``autoreload=True`` option to `tornado.web.Application` + which defaults to True when ``debug=True``). + When using multiple processes, no IOLoops can be created or + referenced until after the call to ``fork_processes``. + + In each child process, ``fork_processes`` returns its *task id*, a + number between 0 and ``num_processes``. Processes that exit + abnormally (due to a signal or non-zero exit status) are restarted + with the same id (up to ``max_restarts`` times). In the parent + process, ``fork_processes`` returns None if all child processes + have exited normally, but will otherwise only exit by throwing an + exception. + + max_restarts defaults to 100. + """ + if max_restarts is None: + max_restarts = 100 + + global _task_id + assert _task_id is None + if num_processes is None or num_processes <= 0: + num_processes = cpu_count() + gen_log.info("Starting %d processes", num_processes) + children = {} + + def start_child(i: int) -> Optional[int]: + pid = os.fork() + if pid == 0: + # child process + _reseed_random() + global _task_id + _task_id = i + return i + else: + children[pid] = i + return None + + for i in range(num_processes): + id = start_child(i) + if id is not None: + return id + num_restarts = 0 + while children: + try: + pid, status = os.wait() + except OSError as e: + if errno_from_exception(e) == errno.EINTR: + continue + raise + if pid not in children: + continue + id = children.pop(pid) + if os.WIFSIGNALED(status): + gen_log.warning( + "child %d (pid %d) killed by signal %d, restarting", + id, + pid, + os.WTERMSIG(status), + ) + elif os.WEXITSTATUS(status) != 0: + gen_log.warning( + "child %d (pid %d) exited with status %d, restarting", + id, + pid, + os.WEXITSTATUS(status), + ) + else: + gen_log.info("child %d (pid %d) exited normally", id, pid) + continue + num_restarts += 1 + if num_restarts > max_restarts: + raise RuntimeError("Too many child restarts, giving up") + new_id = start_child(id) + if new_id is not None: + return new_id + # All child processes exited cleanly, so exit the master process + # instead of just returning to right after the call to + # fork_processes (which will probably just start up another IOLoop + # unless the caller checks the return value). + sys.exit(0) + + +def task_id() -> Optional[int]: + """Returns the current task id, if any. + + Returns None if this process was not created by `fork_processes`. + """ + global _task_id + return _task_id + + +class Subprocess(object): + """Wraps ``subprocess.Popen`` with IOStream support. + + The constructor is the same as ``subprocess.Popen`` with the following + additions: + + * ``stdin``, ``stdout``, and ``stderr`` may have the value + ``tornado.process.Subprocess.STREAM``, which will make the corresponding + attribute of the resulting Subprocess a `.PipeIOStream`. If this option + is used, the caller is responsible for closing the streams when done + with them. + + The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and + ``wait_for_exit`` methods do not work on Windows. There is + therefore no reason to use this class instead of + ``subprocess.Popen`` on that platform. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + + """ + + STREAM = object() + + _initialized = False + _waiting = {} # type: ignore + _old_sigchld = None + + def __init__(self, *args: Any, **kwargs: Any) -> None: + self.io_loop = ioloop.IOLoop.current() + # All FDs we create should be closed on error; those in to_close + # should be closed in the parent process on success. + pipe_fds = [] # type: List[int] + to_close = [] # type: List[int] + if kwargs.get("stdin") is Subprocess.STREAM: + in_r, in_w = _pipe_cloexec() + kwargs["stdin"] = in_r + pipe_fds.extend((in_r, in_w)) + to_close.append(in_r) + self.stdin = PipeIOStream(in_w) + if kwargs.get("stdout") is Subprocess.STREAM: + out_r, out_w = _pipe_cloexec() + kwargs["stdout"] = out_w + pipe_fds.extend((out_r, out_w)) + to_close.append(out_w) + self.stdout = PipeIOStream(out_r) + if kwargs.get("stderr") is Subprocess.STREAM: + err_r, err_w = _pipe_cloexec() + kwargs["stderr"] = err_w + pipe_fds.extend((err_r, err_w)) + to_close.append(err_w) + self.stderr = PipeIOStream(err_r) + try: + self.proc = subprocess.Popen(*args, **kwargs) + except: + for fd in pipe_fds: + os.close(fd) + raise + for fd in to_close: + os.close(fd) + self.pid = self.proc.pid + for attr in ["stdin", "stdout", "stderr"]: + if not hasattr(self, attr): # don't clobber streams set above + setattr(self, attr, getattr(self.proc, attr)) + self._exit_callback = None # type: Optional[Callable[[int], None]] + self.returncode = None # type: Optional[int] + + def set_exit_callback(self, callback: Callable[[int], None]) -> None: + """Runs ``callback`` when this process exits. + + The callback takes one argument, the return code of the process. + + This method uses a ``SIGCHLD`` handler, which is a global setting + and may conflict if you have other libraries trying to handle the + same signal. If you are using more than one ``IOLoop`` it may + be necessary to call `Subprocess.initialize` first to designate + one ``IOLoop`` to run the signal handlers. + + In many cases a close callback on the stdout or stderr streams + can be used as an alternative to an exit callback if the + signal handler is causing a problem. + """ + self._exit_callback = callback + Subprocess.initialize() + Subprocess._waiting[self.pid] = self + Subprocess._try_cleanup_process(self.pid) + + def wait_for_exit(self, raise_error: bool = True) -> "Future[int]": + """Returns a `.Future` which resolves when the process exits. + + Usage:: + + ret = yield proc.wait_for_exit() + + This is a coroutine-friendly alternative to `set_exit_callback` + (and a replacement for the blocking `subprocess.Popen.wait`). + + By default, raises `subprocess.CalledProcessError` if the process + has a non-zero exit status. Use ``wait_for_exit(raise_error=False)`` + to suppress this behavior and return the exit status without raising. + + .. versionadded:: 4.2 + """ + future = Future() # type: Future[int] + + def callback(ret: int) -> None: + if ret != 0 and raise_error: + # Unfortunately we don't have the original args any more. + future_set_exception_unless_cancelled( + future, CalledProcessError(ret, "unknown") + ) + else: + future_set_result_unless_cancelled(future, ret) + + self.set_exit_callback(callback) + return future + + @classmethod + def initialize(cls) -> None: + """Initializes the ``SIGCHLD`` handler. + + The signal handler is run on an `.IOLoop` to avoid locking issues. + Note that the `.IOLoop` used for signal handling need not be the + same one used by individual Subprocess objects (as long as the + ``IOLoops`` are each running in separate threads). + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been + removed. + """ + if cls._initialized: + return + io_loop = ioloop.IOLoop.current() + cls._old_sigchld = signal.signal( + signal.SIGCHLD, + lambda sig, frame: io_loop.add_callback_from_signal(cls._cleanup), + ) + cls._initialized = True + + @classmethod + def uninitialize(cls) -> None: + """Removes the ``SIGCHLD`` handler.""" + if not cls._initialized: + return + signal.signal(signal.SIGCHLD, cls._old_sigchld) + cls._initialized = False + + @classmethod + def _cleanup(cls) -> None: + for pid in list(cls._waiting.keys()): # make a copy + cls._try_cleanup_process(pid) + + @classmethod + def _try_cleanup_process(cls, pid: int) -> None: + try: + ret_pid, status = os.waitpid(pid, os.WNOHANG) + except OSError as e: + if errno_from_exception(e) == errno.ECHILD: + return + if ret_pid == 0: + return + assert ret_pid == pid + subproc = cls._waiting.pop(pid) + subproc.io_loop.add_callback_from_signal(subproc._set_returncode, status) + + def _set_returncode(self, status: int) -> None: + if os.WIFSIGNALED(status): + self.returncode = -os.WTERMSIG(status) + else: + assert os.WIFEXITED(status) + self.returncode = os.WEXITSTATUS(status) + # We've taken over wait() duty from the subprocess.Popen + # object. If we don't inform it of the process's return code, + # it will log a warning at destruction in python 3.6+. + self.proc.returncode = self.returncode + if self._exit_callback: + callback = self._exit_callback + self._exit_callback = None + callback(self.returncode) diff --git a/venv/lib/python3.7/site-packages/tornado/py.typed b/venv/lib/python3.7/site-packages/tornado/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.7/site-packages/tornado/queues.py b/venv/lib/python3.7/site-packages/tornado/queues.py new file mode 100644 index 0000000..19ff6c6 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/queues.py @@ -0,0 +1,410 @@ +# Copyright 2015 The Tornado Authors +# +# 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. + +"""Asynchronous queues for coroutines. These classes are very similar +to those provided in the standard library's `asyncio package +<https://docs.python.org/3/library/asyncio-queue.html>`_. + +.. warning:: + + Unlike the standard library's `queue` module, the classes defined here + are *not* thread-safe. To use these queues from another thread, + use `.IOLoop.add_callback` to transfer control to the `.IOLoop` thread + before calling any queue methods. + +""" + +import collections +import datetime +import heapq + +from tornado import gen, ioloop +from tornado.concurrent import Future, future_set_result_unless_cancelled +from tornado.locks import Event + +from typing import Union, TypeVar, Generic, Awaitable +import typing + +if typing.TYPE_CHECKING: + from typing import Deque, Tuple, List, Any # noqa: F401 + +_T = TypeVar("_T") + +__all__ = ["Queue", "PriorityQueue", "LifoQueue", "QueueFull", "QueueEmpty"] + + +class QueueEmpty(Exception): + """Raised by `.Queue.get_nowait` when the queue has no items.""" + + pass + + +class QueueFull(Exception): + """Raised by `.Queue.put_nowait` when a queue is at its maximum size.""" + + pass + + +def _set_timeout( + future: Future, timeout: Union[None, float, datetime.timedelta] +) -> None: + if timeout: + + def on_timeout() -> None: + if not future.done(): + future.set_exception(gen.TimeoutError()) + + io_loop = ioloop.IOLoop.current() + timeout_handle = io_loop.add_timeout(timeout, on_timeout) + future.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle)) + + +class _QueueIterator(Generic[_T]): + def __init__(self, q: "Queue[_T]") -> None: + self.q = q + + def __anext__(self) -> Awaitable[_T]: + return self.q.get() + + +class Queue(Generic[_T]): + """Coordinate producer and consumer coroutines. + + If maxsize is 0 (the default) the queue size is unbounded. + + .. testcode:: + + from tornado import gen + from tornado.ioloop import IOLoop + from tornado.queues import Queue + + q = Queue(maxsize=2) + + async def consumer(): + async for item in q: + try: + print('Doing work on %s' % item) + await gen.sleep(0.01) + finally: + q.task_done() + + async def producer(): + for item in range(5): + await q.put(item) + print('Put %s' % item) + + async def main(): + # Start consumer without waiting (since it never finishes). + IOLoop.current().spawn_callback(consumer) + await producer() # Wait for producer to put all tasks. + await q.join() # Wait for consumer to finish all tasks. + print('Done') + + IOLoop.current().run_sync(main) + + .. testoutput:: + + Put 0 + Put 1 + Doing work on 0 + Put 2 + Doing work on 1 + Put 3 + Doing work on 2 + Put 4 + Doing work on 3 + Doing work on 4 + Done + + + In versions of Python without native coroutines (before 3.5), + ``consumer()`` could be written as:: + + @gen.coroutine + def consumer(): + while True: + item = yield q.get() + try: + print('Doing work on %s' % item) + yield gen.sleep(0.01) + finally: + q.task_done() + + .. versionchanged:: 4.3 + Added ``async for`` support in Python 3.5. + + """ + + # Exact type depends on subclass. Could be another generic + # parameter and use protocols to be more precise here. + _queue = None # type: Any + + def __init__(self, maxsize: int = 0) -> None: + if maxsize is None: + raise TypeError("maxsize can't be None") + + if maxsize < 0: + raise ValueError("maxsize can't be negative") + + self._maxsize = maxsize + self._init() + self._getters = collections.deque([]) # type: Deque[Future[_T]] + self._putters = collections.deque([]) # type: Deque[Tuple[_T, Future[None]]] + self._unfinished_tasks = 0 + self._finished = Event() + self._finished.set() + + @property + def maxsize(self) -> int: + """Number of items allowed in the queue.""" + return self._maxsize + + def qsize(self) -> int: + """Number of items in the queue.""" + return len(self._queue) + + def empty(self) -> bool: + return not self._queue + + def full(self) -> bool: + if self.maxsize == 0: + return False + else: + return self.qsize() >= self.maxsize + + def put( + self, item: _T, timeout: Union[float, datetime.timedelta] = None + ) -> "Future[None]": + """Put an item into the queue, perhaps waiting until there is room. + + Returns a Future, which raises `tornado.util.TimeoutError` after a + timeout. + + ``timeout`` may be a number denoting a time (on the same + scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a + `datetime.timedelta` object for a deadline relative to the + current time. + """ + future = Future() # type: Future[None] + try: + self.put_nowait(item) + except QueueFull: + self._putters.append((item, future)) + _set_timeout(future, timeout) + else: + future.set_result(None) + return future + + def put_nowait(self, item: _T) -> None: + """Put an item into the queue without blocking. + + If no free slot is immediately available, raise `QueueFull`. + """ + self._consume_expired() + if self._getters: + assert self.empty(), "queue non-empty, why are getters waiting?" + getter = self._getters.popleft() + self.__put_internal(item) + future_set_result_unless_cancelled(getter, self._get()) + elif self.full(): + raise QueueFull + else: + self.__put_internal(item) + + def get(self, timeout: Union[float, datetime.timedelta] = None) -> Awaitable[_T]: + """Remove and return an item from the queue. + + Returns an awaitable which resolves once an item is available, or raises + `tornado.util.TimeoutError` after a timeout. + + ``timeout`` may be a number denoting a time (on the same + scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a + `datetime.timedelta` object for a deadline relative to the + current time. + + .. note:: + + The ``timeout`` argument of this method differs from that + of the standard library's `queue.Queue.get`. That method + interprets numeric values as relative timeouts; this one + interprets them as absolute deadlines and requires + ``timedelta`` objects for relative timeouts (consistent + with other timeouts in Tornado). + + """ + future = Future() # type: Future[_T] + try: + future.set_result(self.get_nowait()) + except QueueEmpty: + self._getters.append(future) + _set_timeout(future, timeout) + return future + + def get_nowait(self) -> _T: + """Remove and return an item from the queue without blocking. + + Return an item if one is immediately available, else raise + `QueueEmpty`. + """ + self._consume_expired() + if self._putters: + assert self.full(), "queue not full, why are putters waiting?" + item, putter = self._putters.popleft() + self.__put_internal(item) + future_set_result_unless_cancelled(putter, None) + return self._get() + elif self.qsize(): + return self._get() + else: + raise QueueEmpty + + def task_done(self) -> None: + """Indicate that a formerly enqueued task is complete. + + Used by queue consumers. For each `.get` used to fetch a task, a + subsequent call to `.task_done` tells the queue that the processing + on the task is complete. + + If a `.join` is blocking, it resumes when all items have been + processed; that is, when every `.put` is matched by a `.task_done`. + + Raises `ValueError` if called more times than `.put`. + """ + if self._unfinished_tasks <= 0: + raise ValueError("task_done() called too many times") + self._unfinished_tasks -= 1 + if self._unfinished_tasks == 0: + self._finished.set() + + def join(self, timeout: Union[float, datetime.timedelta] = None) -> Awaitable[None]: + """Block until all items in the queue are processed. + + Returns an awaitable, which raises `tornado.util.TimeoutError` after a + timeout. + """ + return self._finished.wait(timeout) + + def __aiter__(self) -> _QueueIterator[_T]: + return _QueueIterator(self) + + # These three are overridable in subclasses. + def _init(self) -> None: + self._queue = collections.deque() + + def _get(self) -> _T: + return self._queue.popleft() + + def _put(self, item: _T) -> None: + self._queue.append(item) + + # End of the overridable methods. + + def __put_internal(self, item: _T) -> None: + self._unfinished_tasks += 1 + self._finished.clear() + self._put(item) + + def _consume_expired(self) -> None: + # Remove timed-out waiters. + while self._putters and self._putters[0][1].done(): + self._putters.popleft() + + while self._getters and self._getters[0].done(): + self._getters.popleft() + + def __repr__(self) -> str: + return "<%s at %s %s>" % (type(self).__name__, hex(id(self)), self._format()) + + def __str__(self) -> str: + return "<%s %s>" % (type(self).__name__, self._format()) + + def _format(self) -> str: + result = "maxsize=%r" % (self.maxsize,) + if getattr(self, "_queue", None): + result += " queue=%r" % self._queue + if self._getters: + result += " getters[%s]" % len(self._getters) + if self._putters: + result += " putters[%s]" % len(self._putters) + if self._unfinished_tasks: + result += " tasks=%s" % self._unfinished_tasks + return result + + +class PriorityQueue(Queue): + """A `.Queue` that retrieves entries in priority order, lowest first. + + Entries are typically tuples like ``(priority number, data)``. + + .. testcode:: + + from tornado.queues import PriorityQueue + + q = PriorityQueue() + q.put((1, 'medium-priority item')) + q.put((0, 'high-priority item')) + q.put((10, 'low-priority item')) + + print(q.get_nowait()) + print(q.get_nowait()) + print(q.get_nowait()) + + .. testoutput:: + + (0, 'high-priority item') + (1, 'medium-priority item') + (10, 'low-priority item') + """ + + def _init(self) -> None: + self._queue = [] + + def _put(self, item: _T) -> None: + heapq.heappush(self._queue, item) + + def _get(self) -> _T: + return heapq.heappop(self._queue) + + +class LifoQueue(Queue): + """A `.Queue` that retrieves the most recently put items first. + + .. testcode:: + + from tornado.queues import LifoQueue + + q = LifoQueue() + q.put(3) + q.put(2) + q.put(1) + + print(q.get_nowait()) + print(q.get_nowait()) + print(q.get_nowait()) + + .. testoutput:: + + 1 + 2 + 3 + """ + + def _init(self) -> None: + self._queue = [] + + def _put(self, item: _T) -> None: + self._queue.append(item) + + def _get(self) -> _T: + return self._queue.pop() diff --git a/venv/lib/python3.7/site-packages/tornado/routing.py b/venv/lib/python3.7/site-packages/tornado/routing.py new file mode 100644 index 0000000..ae7abcd --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/routing.py @@ -0,0 +1,711 @@ +# Copyright 2015 The Tornado Authors +# +# 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. + +"""Flexible routing implementation. + +Tornado routes HTTP requests to appropriate handlers using `Router` +class implementations. The `tornado.web.Application` class is a +`Router` implementation and may be used directly, or the classes in +this module may be used for additional flexibility. The `RuleRouter` +class can match on more criteria than `.Application`, or the `Router` +interface can be subclassed for maximum customization. + +`Router` interface extends `~.httputil.HTTPServerConnectionDelegate` +to provide additional routing capabilities. This also means that any +`Router` implementation can be used directly as a ``request_callback`` +for `~.httpserver.HTTPServer` constructor. + +`Router` subclass must implement a ``find_handler`` method to provide +a suitable `~.httputil.HTTPMessageDelegate` instance to handle the +request: + +.. code-block:: python + + class CustomRouter(Router): + def find_handler(self, request, **kwargs): + # some routing logic providing a suitable HTTPMessageDelegate instance + return MessageDelegate(request.connection) + + class MessageDelegate(HTTPMessageDelegate): + def __init__(self, connection): + self.connection = connection + + def finish(self): + self.connection.write_headers( + ResponseStartLine("HTTP/1.1", 200, "OK"), + HTTPHeaders({"Content-Length": "2"}), + b"OK") + self.connection.finish() + + router = CustomRouter() + server = HTTPServer(router) + +The main responsibility of `Router` implementation is to provide a +mapping from a request to `~.httputil.HTTPMessageDelegate` instance +that will handle this request. In the example above we can see that +routing is possible even without instantiating an `~.web.Application`. + +For routing to `~.web.RequestHandler` implementations we need an +`~.web.Application` instance. `~.web.Application.get_handler_delegate` +provides a convenient way to create `~.httputil.HTTPMessageDelegate` +for a given request and `~.web.RequestHandler`. + +Here is a simple example of how we can we route to +`~.web.RequestHandler` subclasses by HTTP method: + +.. code-block:: python + + resources = {} + + class GetResource(RequestHandler): + def get(self, path): + if path not in resources: + raise HTTPError(404) + + self.finish(resources[path]) + + class PostResource(RequestHandler): + def post(self, path): + resources[path] = self.request.body + + class HTTPMethodRouter(Router): + def __init__(self, app): + self.app = app + + def find_handler(self, request, **kwargs): + handler = GetResource if request.method == "GET" else PostResource + return self.app.get_handler_delegate(request, handler, path_args=[request.path]) + + router = HTTPMethodRouter(Application()) + server = HTTPServer(router) + +`ReversibleRouter` interface adds the ability to distinguish between +the routes and reverse them to the original urls using route's name +and additional arguments. `~.web.Application` is itself an +implementation of `ReversibleRouter` class. + +`RuleRouter` and `ReversibleRuleRouter` are implementations of +`Router` and `ReversibleRouter` interfaces and can be used for +creating rule-based routing configurations. + +Rules are instances of `Rule` class. They contain a `Matcher`, which +provides the logic for determining whether the rule is a match for a +particular request and a target, which can be one of the following. + +1) An instance of `~.httputil.HTTPServerConnectionDelegate`: + +.. code-block:: python + + router = RuleRouter([ + Rule(PathMatches("/handler"), ConnectionDelegate()), + # ... more rules + ]) + + class ConnectionDelegate(HTTPServerConnectionDelegate): + def start_request(self, server_conn, request_conn): + return MessageDelegate(request_conn) + +2) A callable accepting a single argument of `~.httputil.HTTPServerRequest` type: + +.. code-block:: python + + router = RuleRouter([ + Rule(PathMatches("/callable"), request_callable) + ]) + + def request_callable(request): + request.write(b"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK") + request.finish() + +3) Another `Router` instance: + +.. code-block:: python + + router = RuleRouter([ + Rule(PathMatches("/router.*"), CustomRouter()) + ]) + +Of course a nested `RuleRouter` or a `~.web.Application` is allowed: + +.. code-block:: python + + router = RuleRouter([ + Rule(HostMatches("example.com"), RuleRouter([ + Rule(PathMatches("/app1/.*"), Application([(r"/app1/handler", Handler)]))), + ])) + ]) + + server = HTTPServer(router) + +In the example below `RuleRouter` is used to route between applications: + +.. code-block:: python + + app1 = Application([ + (r"/app1/handler", Handler1), + # other handlers ... + ]) + + app2 = Application([ + (r"/app2/handler", Handler2), + # other handlers ... + ]) + + router = RuleRouter([ + Rule(PathMatches("/app1.*"), app1), + Rule(PathMatches("/app2.*"), app2) + ]) + + server = HTTPServer(router) + +For more information on application-level routing see docs for `~.web.Application`. + +.. versionadded:: 4.5 + +""" + +import re +from functools import partial + +from tornado import httputil +from tornado.httpserver import _CallableAdapter +from tornado.escape import url_escape, url_unescape, utf8 +from tornado.log import app_log +from tornado.util import basestring_type, import_object, re_unescape, unicode_type + +from typing import Any, Union, Optional, Awaitable, List, Dict, Pattern, Tuple, overload + + +class Router(httputil.HTTPServerConnectionDelegate): + """Abstract router interface.""" + + def find_handler( + self, request: httputil.HTTPServerRequest, **kwargs: Any + ) -> Optional[httputil.HTTPMessageDelegate]: + """Must be implemented to return an appropriate instance of `~.httputil.HTTPMessageDelegate` + that can serve the request. + Routing implementations may pass additional kwargs to extend the routing logic. + + :arg httputil.HTTPServerRequest request: current HTTP request. + :arg kwargs: additional keyword arguments passed by routing implementation. + :returns: an instance of `~.httputil.HTTPMessageDelegate` that will be used to + process the request. + """ + raise NotImplementedError() + + def start_request( + self, server_conn: object, request_conn: httputil.HTTPConnection + ) -> httputil.HTTPMessageDelegate: + return _RoutingDelegate(self, server_conn, request_conn) + + +class ReversibleRouter(Router): + """Abstract router interface for routers that can handle named routes + and support reversing them to original urls. + """ + + def reverse_url(self, name: str, *args: Any) -> Optional[str]: + """Returns url string for a given route name and arguments + or ``None`` if no match is found. + + :arg str name: route name. + :arg args: url parameters. + :returns: parametrized url string for a given route name (or ``None``). + """ + raise NotImplementedError() + + +class _RoutingDelegate(httputil.HTTPMessageDelegate): + def __init__( + self, router: Router, server_conn: object, request_conn: httputil.HTTPConnection + ) -> None: + self.server_conn = server_conn + self.request_conn = request_conn + self.delegate = None # type: Optional[httputil.HTTPMessageDelegate] + self.router = router # type: Router + + def headers_received( + self, + start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], + headers: httputil.HTTPHeaders, + ) -> Optional[Awaitable[None]]: + assert isinstance(start_line, httputil.RequestStartLine) + request = httputil.HTTPServerRequest( + connection=self.request_conn, + server_connection=self.server_conn, + start_line=start_line, + headers=headers, + ) + + self.delegate = self.router.find_handler(request) + if self.delegate is None: + app_log.debug( + "Delegate for %s %s request not found", + start_line.method, + start_line.path, + ) + self.delegate = _DefaultMessageDelegate(self.request_conn) + + return self.delegate.headers_received(start_line, headers) + + def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: + assert self.delegate is not None + return self.delegate.data_received(chunk) + + def finish(self) -> None: + assert self.delegate is not None + self.delegate.finish() + + def on_connection_close(self) -> None: + assert self.delegate is not None + self.delegate.on_connection_close() + + +class _DefaultMessageDelegate(httputil.HTTPMessageDelegate): + def __init__(self, connection: httputil.HTTPConnection) -> None: + self.connection = connection + + def finish(self) -> None: + self.connection.write_headers( + httputil.ResponseStartLine("HTTP/1.1", 404, "Not Found"), + httputil.HTTPHeaders(), + ) + self.connection.finish() + + +# _RuleList can either contain pre-constructed Rules or a sequence of +# arguments to be passed to the Rule constructor. +_RuleList = List[ + Union[ + "Rule", + List[Any], # Can't do detailed typechecking of lists. + Tuple[Union[str, "Matcher"], Any], + Tuple[Union[str, "Matcher"], Any, Dict[str, Any]], + Tuple[Union[str, "Matcher"], Any, Dict[str, Any], str], + ] +] + + +class RuleRouter(Router): + """Rule-based router implementation.""" + + def __init__(self, rules: _RuleList = None) -> None: + """Constructs a router from an ordered list of rules:: + + RuleRouter([ + Rule(PathMatches("/handler"), Target), + # ... more rules + ]) + + You can also omit explicit `Rule` constructor and use tuples of arguments:: + + RuleRouter([ + (PathMatches("/handler"), Target), + ]) + + `PathMatches` is a default matcher, so the example above can be simplified:: + + RuleRouter([ + ("/handler", Target), + ]) + + In the examples above, ``Target`` can be a nested `Router` instance, an instance of + `~.httputil.HTTPServerConnectionDelegate` or an old-style callable, + accepting a request argument. + + :arg rules: a list of `Rule` instances or tuples of `Rule` + constructor arguments. + """ + self.rules = [] # type: List[Rule] + if rules: + self.add_rules(rules) + + def add_rules(self, rules: _RuleList) -> None: + """Appends new rules to the router. + + :arg rules: a list of Rule instances (or tuples of arguments, which are + passed to Rule constructor). + """ + for rule in rules: + if isinstance(rule, (tuple, list)): + assert len(rule) in (2, 3, 4) + if isinstance(rule[0], basestring_type): + rule = Rule(PathMatches(rule[0]), *rule[1:]) + else: + rule = Rule(*rule) + + self.rules.append(self.process_rule(rule)) + + def process_rule(self, rule: "Rule") -> "Rule": + """Override this method for additional preprocessing of each rule. + + :arg Rule rule: a rule to be processed. + :returns: the same or modified Rule instance. + """ + return rule + + def find_handler( + self, request: httputil.HTTPServerRequest, **kwargs: Any + ) -> Optional[httputil.HTTPMessageDelegate]: + for rule in self.rules: + target_params = rule.matcher.match(request) + if target_params is not None: + if rule.target_kwargs: + target_params["target_kwargs"] = rule.target_kwargs + + delegate = self.get_target_delegate( + rule.target, request, **target_params + ) + + if delegate is not None: + return delegate + + return None + + def get_target_delegate( + self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any + ) -> Optional[httputil.HTTPMessageDelegate]: + """Returns an instance of `~.httputil.HTTPMessageDelegate` for a + Rule's target. This method is called by `~.find_handler` and can be + extended to provide additional target types. + + :arg target: a Rule's target. + :arg httputil.HTTPServerRequest request: current request. + :arg target_params: additional parameters that can be useful + for `~.httputil.HTTPMessageDelegate` creation. + """ + if isinstance(target, Router): + return target.find_handler(request, **target_params) + + elif isinstance(target, httputil.HTTPServerConnectionDelegate): + assert request.connection is not None + return target.start_request(request.server_connection, request.connection) + + elif callable(target): + assert request.connection is not None + return _CallableAdapter( + partial(target, **target_params), request.connection + ) + + return None + + +class ReversibleRuleRouter(ReversibleRouter, RuleRouter): + """A rule-based router that implements ``reverse_url`` method. + + Each rule added to this router may have a ``name`` attribute that can be + used to reconstruct an original uri. The actual reconstruction takes place + in a rule's matcher (see `Matcher.reverse`). + """ + + def __init__(self, rules: _RuleList = None) -> None: + self.named_rules = {} # type: Dict[str, Any] + super(ReversibleRuleRouter, self).__init__(rules) + + def process_rule(self, rule: "Rule") -> "Rule": + rule = super(ReversibleRuleRouter, self).process_rule(rule) + + if rule.name: + if rule.name in self.named_rules: + app_log.warning( + "Multiple handlers named %s; replacing previous value", rule.name + ) + self.named_rules[rule.name] = rule + + return rule + + def reverse_url(self, name: str, *args: Any) -> Optional[str]: + if name in self.named_rules: + return self.named_rules[name].matcher.reverse(*args) + + for rule in self.rules: + if isinstance(rule.target, ReversibleRouter): + reversed_url = rule.target.reverse_url(name, *args) + if reversed_url is not None: + return reversed_url + + return None + + +class Rule(object): + """A routing rule.""" + + def __init__( + self, + matcher: "Matcher", + target: Any, + target_kwargs: Dict[str, Any] = None, + name: str = None, + ) -> None: + """Constructs a Rule instance. + + :arg Matcher matcher: a `Matcher` instance used for determining + whether the rule should be considered a match for a specific + request. + :arg target: a Rule's target (typically a ``RequestHandler`` or + `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`, + depending on routing implementation). + :arg dict target_kwargs: a dict of parameters that can be useful + at the moment of target instantiation (for example, ``status_code`` + for a ``RequestHandler`` subclass). They end up in + ``target_params['target_kwargs']`` of `RuleRouter.get_target_delegate` + method. + :arg str name: the name of the rule that can be used to find it + in `ReversibleRouter.reverse_url` implementation. + """ + if isinstance(target, str): + # import the Module and instantiate the class + # Must be a fully qualified name (module.ClassName) + target = import_object(target) + + self.matcher = matcher # type: Matcher + self.target = target + self.target_kwargs = target_kwargs if target_kwargs else {} + self.name = name + + def reverse(self, *args: Any) -> Optional[str]: + return self.matcher.reverse(*args) + + def __repr__(self) -> str: + return "%s(%r, %s, kwargs=%r, name=%r)" % ( + self.__class__.__name__, + self.matcher, + self.target, + self.target_kwargs, + self.name, + ) + + +class Matcher(object): + """Represents a matcher for request features.""" + + def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: + """Matches current instance against the request. + + :arg httputil.HTTPServerRequest request: current HTTP request + :returns: a dict of parameters to be passed to the target handler + (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs`` + can be passed for proper `~.web.RequestHandler` instantiation). + An empty dict is a valid (and common) return value to indicate a match + when the argument-passing features are not used. + ``None`` must be returned to indicate that there is no match.""" + raise NotImplementedError() + + def reverse(self, *args: Any) -> Optional[str]: + """Reconstructs full url from matcher instance and additional arguments.""" + return None + + +class AnyMatches(Matcher): + """Matches any request.""" + + def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: + return {} + + +class HostMatches(Matcher): + """Matches requests from hosts specified by ``host_pattern`` regex.""" + + def __init__(self, host_pattern: Union[str, Pattern]) -> None: + if isinstance(host_pattern, basestring_type): + if not host_pattern.endswith("$"): + host_pattern += "$" + self.host_pattern = re.compile(host_pattern) + else: + self.host_pattern = host_pattern + + def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: + if self.host_pattern.match(request.host_name): + return {} + + return None + + +class DefaultHostMatches(Matcher): + """Matches requests from host that is equal to application's default_host. + Always returns no match if ``X-Real-Ip`` header is present. + """ + + def __init__(self, application: Any, host_pattern: Pattern) -> None: + self.application = application + self.host_pattern = host_pattern + + def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: + # Look for default host if not behind load balancer (for debugging) + if "X-Real-Ip" not in request.headers: + if self.host_pattern.match(self.application.default_host): + return {} + return None + + +class PathMatches(Matcher): + """Matches requests with paths specified by ``path_pattern`` regex.""" + + def __init__(self, path_pattern: Union[str, Pattern]) -> None: + if isinstance(path_pattern, basestring_type): + if not path_pattern.endswith("$"): + path_pattern += "$" + self.regex = re.compile(path_pattern) + else: + self.regex = path_pattern + + assert len(self.regex.groupindex) in (0, self.regex.groups), ( + "groups in url regexes must either be all named or all " + "positional: %r" % self.regex.pattern + ) + + self._path, self._group_count = self._find_groups() + + def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: + match = self.regex.match(request.path) + if match is None: + return None + if not self.regex.groups: + return {} + + path_args = [] # type: List[bytes] + path_kwargs = {} # type: Dict[str, bytes] + + # Pass matched groups to the handler. Since + # match.groups() includes both named and + # unnamed groups, we want to use either groups + # or groupdict but not both. + if self.regex.groupindex: + path_kwargs = dict( + (str(k), _unquote_or_none(v)) for (k, v) in match.groupdict().items() + ) + else: + path_args = [_unquote_or_none(s) for s in match.groups()] + + return dict(path_args=path_args, path_kwargs=path_kwargs) + + def reverse(self, *args: Any) -> Optional[str]: + if self._path is None: + raise ValueError("Cannot reverse url regex " + self.regex.pattern) + assert len(args) == self._group_count, ( + "required number of arguments " "not found" + ) + if not len(args): + return self._path + converted_args = [] + for a in args: + if not isinstance(a, (unicode_type, bytes)): + a = str(a) + converted_args.append(url_escape(utf8(a), plus=False)) + return self._path % tuple(converted_args) + + def _find_groups(self) -> Tuple[Optional[str], Optional[int]]: + """Returns a tuple (reverse string, group count) for a url. + + For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method + would return ('/%s/%s/', 2). + """ + pattern = self.regex.pattern + if pattern.startswith("^"): + pattern = pattern[1:] + if pattern.endswith("$"): + pattern = pattern[:-1] + + if self.regex.groups != pattern.count("("): + # The pattern is too complicated for our simplistic matching, + # so we can't support reversing it. + return None, None + + pieces = [] + for fragment in pattern.split("("): + if ")" in fragment: + paren_loc = fragment.index(")") + if paren_loc >= 0: + pieces.append("%s" + fragment[paren_loc + 1 :]) + else: + try: + unescaped_fragment = re_unescape(fragment) + except ValueError: + # If we can't unescape part of it, we can't + # reverse this url. + return (None, None) + pieces.append(unescaped_fragment) + + return "".join(pieces), self.regex.groups + + +class URLSpec(Rule): + """Specifies mappings between URLs and handlers. + + .. versionchanged: 4.5 + `URLSpec` is now a subclass of a `Rule` with `PathMatches` matcher and is preserved for + backwards compatibility. + """ + + def __init__( + self, + pattern: Union[str, Pattern], + handler: Any, + kwargs: Dict[str, Any] = None, + name: str = None, + ) -> None: + """Parameters: + + * ``pattern``: Regular expression to be matched. Any capturing + groups in the regex will be passed in to the handler's + get/post/etc methods as arguments (by keyword if named, by + position if unnamed. Named and unnamed capturing groups + may not be mixed in the same rule). + + * ``handler``: `~.web.RequestHandler` subclass to be invoked. + + * ``kwargs`` (optional): A dictionary of additional arguments + to be passed to the handler's constructor. + + * ``name`` (optional): A name for this handler. Used by + `~.web.Application.reverse_url`. + + """ + matcher = PathMatches(pattern) + super(URLSpec, self).__init__(matcher, handler, kwargs, name) + + self.regex = matcher.regex + self.handler_class = self.target + self.kwargs = kwargs + + def __repr__(self) -> str: + return "%s(%r, %s, kwargs=%r, name=%r)" % ( + self.__class__.__name__, + self.regex.pattern, + self.handler_class, + self.kwargs, + self.name, + ) + + +@overload +def _unquote_or_none(s: str) -> bytes: + pass + + +@overload # noqa: F811 +def _unquote_or_none(s: None) -> None: + pass + + +def _unquote_or_none(s: Optional[str]) -> Optional[bytes]: # noqa: F811 + """None-safe wrapper around url_unescape to handle unmatched optional + groups correctly. + + Note that args are passed as bytes so the handler can decide what + encoding to use. + """ + if s is None: + return s + return url_unescape(s, encoding=None, plus=False) diff --git a/venv/lib/python3.7/site-packages/tornado/simple_httpclient.py b/venv/lib/python3.7/site-packages/tornado/simple_httpclient.py new file mode 100644 index 0000000..1d82890 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/simple_httpclient.py @@ -0,0 +1,692 @@ +from tornado.escape import _unicode +from tornado import gen +from tornado.httpclient import ( + HTTPResponse, + HTTPError, + AsyncHTTPClient, + main, + _RequestProxy, + HTTPRequest, +) +from tornado import httputil +from tornado.http1connection import HTTP1Connection, HTTP1ConnectionParameters +from tornado.ioloop import IOLoop +from tornado.iostream import StreamClosedError, IOStream +from tornado.netutil import ( + Resolver, + OverrideResolver, + _client_ssl_defaults, + is_valid_ip, +) +from tornado.log import gen_log +from tornado.tcpclient import TCPClient + +import base64 +import collections +import copy +import functools +import re +import socket +import ssl +import sys +import time +from io import BytesIO +import urllib.parse + +from typing import Dict, Any, Callable, Optional, Type, Union +from types import TracebackType +import typing + +if typing.TYPE_CHECKING: + from typing import Deque, Tuple, List # noqa: F401 + + +class HTTPTimeoutError(HTTPError): + """Error raised by SimpleAsyncHTTPClient on timeout. + + For historical reasons, this is a subclass of `.HTTPClientError` + which simulates a response code of 599. + + .. versionadded:: 5.1 + """ + + def __init__(self, message: str) -> None: + super(HTTPTimeoutError, self).__init__(599, message=message) + + def __str__(self) -> str: + return self.message or "Timeout" + + +class HTTPStreamClosedError(HTTPError): + """Error raised by SimpleAsyncHTTPClient when the underlying stream is closed. + + When a more specific exception is available (such as `ConnectionResetError`), + it may be raised instead of this one. + + For historical reasons, this is a subclass of `.HTTPClientError` + which simulates a response code of 599. + + .. versionadded:: 5.1 + """ + + def __init__(self, message: str) -> None: + super(HTTPStreamClosedError, self).__init__(599, message=message) + + def __str__(self) -> str: + return self.message or "Stream closed" + + +class SimpleAsyncHTTPClient(AsyncHTTPClient): + """Non-blocking HTTP client with no external dependencies. + + This class implements an HTTP 1.1 client on top of Tornado's IOStreams. + Some features found in the curl-based AsyncHTTPClient are not yet + supported. In particular, proxies are not supported, connections + are not reused, and callers cannot select the network interface to be + used. + """ + + def initialize( # type: ignore + self, + max_clients: int = 10, + hostname_mapping: Dict[str, str] = None, + max_buffer_size: int = 104857600, + resolver: Resolver = None, + defaults: Dict[str, Any] = None, + max_header_size: int = None, + max_body_size: int = None, + ) -> None: + """Creates a AsyncHTTPClient. + + Only a single AsyncHTTPClient instance exists per IOLoop + in order to provide limitations on the number of pending connections. + ``force_instance=True`` may be used to suppress this behavior. + + Note that because of this implicit reuse, unless ``force_instance`` + is used, only the first call to the constructor actually uses + its arguments. It is recommended to use the ``configure`` method + instead of the constructor to ensure that arguments take effect. + + ``max_clients`` is the number of concurrent requests that can be + in progress; when this limit is reached additional requests will be + queued. Note that time spent waiting in this queue still counts + against the ``request_timeout``. + + ``hostname_mapping`` is a dictionary mapping hostnames to IP addresses. + It can be used to make local DNS changes when modifying system-wide + settings like ``/etc/hosts`` is not possible or desirable (e.g. in + unittests). + + ``max_buffer_size`` (default 100MB) is the number of bytes + that can be read into memory at once. ``max_body_size`` + (defaults to ``max_buffer_size``) is the largest response body + that the client will accept. Without a + ``streaming_callback``, the smaller of these two limits + applies; with a ``streaming_callback`` only ``max_body_size`` + does. + + .. versionchanged:: 4.2 + Added the ``max_body_size`` argument. + """ + super(SimpleAsyncHTTPClient, self).initialize(defaults=defaults) + self.max_clients = max_clients + self.queue = ( + collections.deque() + ) # type: Deque[Tuple[object, HTTPRequest, Callable[[HTTPResponse], None]]] + self.active = ( + {} + ) # type: Dict[object, Tuple[HTTPRequest, Callable[[HTTPResponse], None]]] + self.waiting = ( + {} + ) # type: Dict[object, Tuple[HTTPRequest, Callable[[HTTPResponse], None], object]] + self.max_buffer_size = max_buffer_size + self.max_header_size = max_header_size + self.max_body_size = max_body_size + # TCPClient could create a Resolver for us, but we have to do it + # ourselves to support hostname_mapping. + if resolver: + self.resolver = resolver + self.own_resolver = False + else: + self.resolver = Resolver() + self.own_resolver = True + if hostname_mapping is not None: + self.resolver = OverrideResolver( + resolver=self.resolver, mapping=hostname_mapping + ) + self.tcp_client = TCPClient(resolver=self.resolver) + + def close(self) -> None: + super(SimpleAsyncHTTPClient, self).close() + if self.own_resolver: + self.resolver.close() + self.tcp_client.close() + + def fetch_impl( + self, request: HTTPRequest, callback: Callable[[HTTPResponse], None] + ) -> None: + key = object() + self.queue.append((key, request, callback)) + if not len(self.active) < self.max_clients: + assert request.connect_timeout is not None + assert request.request_timeout is not None + timeout_handle = self.io_loop.add_timeout( + self.io_loop.time() + + min(request.connect_timeout, request.request_timeout), + functools.partial(self._on_timeout, key, "in request queue"), + ) + else: + timeout_handle = None + self.waiting[key] = (request, callback, timeout_handle) + self._process_queue() + if self.queue: + gen_log.debug( + "max_clients limit reached, request queued. " + "%d active, %d queued requests." % (len(self.active), len(self.queue)) + ) + + def _process_queue(self) -> None: + while self.queue and len(self.active) < self.max_clients: + key, request, callback = self.queue.popleft() + if key not in self.waiting: + continue + self._remove_timeout(key) + self.active[key] = (request, callback) + release_callback = functools.partial(self._release_fetch, key) + self._handle_request(request, release_callback, callback) + + def _connection_class(self) -> type: + return _HTTPConnection + + def _handle_request( + self, + request: HTTPRequest, + release_callback: Callable[[], None], + final_callback: Callable[[HTTPResponse], None], + ) -> None: + self._connection_class()( + self, + request, + release_callback, + final_callback, + self.max_buffer_size, + self.tcp_client, + self.max_header_size, + self.max_body_size, + ) + + def _release_fetch(self, key: object) -> None: + del self.active[key] + self._process_queue() + + def _remove_timeout(self, key: object) -> None: + if key in self.waiting: + request, callback, timeout_handle = self.waiting[key] + if timeout_handle is not None: + self.io_loop.remove_timeout(timeout_handle) + del self.waiting[key] + + def _on_timeout(self, key: object, info: str = None) -> None: + """Timeout callback of request. + + Construct a timeout HTTPResponse when a timeout occurs. + + :arg object key: A simple object to mark the request. + :info string key: More detailed timeout information. + """ + request, callback, timeout_handle = self.waiting[key] + self.queue.remove((key, request, callback)) + + error_message = "Timeout {0}".format(info) if info else "Timeout" + timeout_response = HTTPResponse( + request, + 599, + error=HTTPTimeoutError(error_message), + request_time=self.io_loop.time() - request.start_time, + ) + self.io_loop.add_callback(callback, timeout_response) + del self.waiting[key] + + +class _HTTPConnection(httputil.HTTPMessageDelegate): + _SUPPORTED_METHODS = set( + ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] + ) + + def __init__( + self, + client: Optional[SimpleAsyncHTTPClient], + request: HTTPRequest, + release_callback: Callable[[], None], + final_callback: Callable[[HTTPResponse], None], + max_buffer_size: int, + tcp_client: TCPClient, + max_header_size: int, + max_body_size: int, + ) -> None: + self.io_loop = IOLoop.current() + self.start_time = self.io_loop.time() + self.start_wall_time = time.time() + self.client = client + self.request = request + self.release_callback = release_callback + self.final_callback = final_callback + self.max_buffer_size = max_buffer_size + self.tcp_client = tcp_client + self.max_header_size = max_header_size + self.max_body_size = max_body_size + self.code = None # type: Optional[int] + self.headers = None # type: Optional[httputil.HTTPHeaders] + self.chunks = [] # type: List[bytes] + self._decompressor = None + # Timeout handle returned by IOLoop.add_timeout + self._timeout = None # type: object + self._sockaddr = None + IOLoop.current().add_future( + gen.convert_yielded(self.run()), lambda f: f.result() + ) + + async def run(self) -> None: + try: + self.parsed = urllib.parse.urlsplit(_unicode(self.request.url)) + if self.parsed.scheme not in ("http", "https"): + raise ValueError("Unsupported url scheme: %s" % self.request.url) + # urlsplit results have hostname and port results, but they + # didn't support ipv6 literals until python 2.7. + netloc = self.parsed.netloc + if "@" in netloc: + userpass, _, netloc = netloc.rpartition("@") + host, port = httputil.split_host_and_port(netloc) + if port is None: + port = 443 if self.parsed.scheme == "https" else 80 + if re.match(r"^\[.*\]$", host): + # raw ipv6 addresses in urls are enclosed in brackets + host = host[1:-1] + self.parsed_hostname = host # save final host for _on_connect + + if self.request.allow_ipv6 is False: + af = socket.AF_INET + else: + af = socket.AF_UNSPEC + + ssl_options = self._get_ssl_options(self.parsed.scheme) + + source_ip = None + if self.request.network_interface: + if is_valid_ip(self.request.network_interface): + source_ip = self.request.network_interface + else: + raise ValueError( + "Unrecognized IPv4 or IPv6 address for network_interface, got %r" + % (self.request.network_interface,) + ) + + timeout = min(self.request.connect_timeout, self.request.request_timeout) + if timeout: + self._timeout = self.io_loop.add_timeout( + self.start_time + timeout, + functools.partial(self._on_timeout, "while connecting"), + ) + stream = await self.tcp_client.connect( + host, + port, + af=af, + ssl_options=ssl_options, + max_buffer_size=self.max_buffer_size, + source_ip=source_ip, + ) + + if self.final_callback is None: + # final_callback is cleared if we've hit our timeout. + stream.close() + return + self.stream = stream + self.stream.set_close_callback(self.on_connection_close) + self._remove_timeout() + if self.final_callback is None: + return + if self.request.request_timeout: + self._timeout = self.io_loop.add_timeout( + self.start_time + self.request.request_timeout, + functools.partial(self._on_timeout, "during request"), + ) + if ( + self.request.method not in self._SUPPORTED_METHODS + and not self.request.allow_nonstandard_methods + ): + raise KeyError("unknown method %s" % self.request.method) + for key in ( + "proxy_host", + "proxy_port", + "proxy_username", + "proxy_password", + "proxy_auth_mode", + ): + if getattr(self.request, key, None): + raise NotImplementedError("%s not supported" % key) + if "Connection" not in self.request.headers: + self.request.headers["Connection"] = "close" + if "Host" not in self.request.headers: + if "@" in self.parsed.netloc: + self.request.headers["Host"] = self.parsed.netloc.rpartition( + "@" + )[-1] + else: + self.request.headers["Host"] = self.parsed.netloc + username, password = None, None + if self.parsed.username is not None: + username, password = self.parsed.username, self.parsed.password + elif self.request.auth_username is not None: + username = self.request.auth_username + password = self.request.auth_password or "" + if username is not None: + assert password is not None + if self.request.auth_mode not in (None, "basic"): + raise ValueError( + "unsupported auth_mode %s", self.request.auth_mode + ) + self.request.headers["Authorization"] = "Basic " + _unicode( + base64.b64encode( + httputil.encode_username_password(username, password) + ) + ) + if self.request.user_agent: + self.request.headers["User-Agent"] = self.request.user_agent + if not self.request.allow_nonstandard_methods: + # Some HTTP methods nearly always have bodies while others + # almost never do. Fail in this case unless the user has + # opted out of sanity checks with allow_nonstandard_methods. + body_expected = self.request.method in ("POST", "PATCH", "PUT") + body_present = ( + self.request.body is not None + or self.request.body_producer is not None + ) + if (body_expected and not body_present) or ( + body_present and not body_expected + ): + raise ValueError( + "Body must %sbe None for method %s (unless " + "allow_nonstandard_methods is true)" + % ("not " if body_expected else "", self.request.method) + ) + if self.request.expect_100_continue: + self.request.headers["Expect"] = "100-continue" + if self.request.body is not None: + # When body_producer is used the caller is responsible for + # setting Content-Length (or else chunked encoding will be used). + self.request.headers["Content-Length"] = str(len(self.request.body)) + if ( + self.request.method == "POST" + and "Content-Type" not in self.request.headers + ): + self.request.headers[ + "Content-Type" + ] = "application/x-www-form-urlencoded" + if self.request.decompress_response: + self.request.headers["Accept-Encoding"] = "gzip" + req_path = (self.parsed.path or "/") + ( + ("?" + self.parsed.query) if self.parsed.query else "" + ) + self.connection = self._create_connection(stream) + start_line = httputil.RequestStartLine( + self.request.method, req_path, "" + ) + self.connection.write_headers(start_line, self.request.headers) + if self.request.expect_100_continue: + await self.connection.read_response(self) + else: + await self._write_body(True) + except Exception: + if not self._handle_exception(*sys.exc_info()): + raise + + def _get_ssl_options( + self, scheme: str + ) -> Union[None, Dict[str, Any], ssl.SSLContext]: + if scheme == "https": + if self.request.ssl_options is not None: + return self.request.ssl_options + # If we are using the defaults, don't construct a + # new SSLContext. + if ( + self.request.validate_cert + and self.request.ca_certs is None + and self.request.client_cert is None + and self.request.client_key is None + ): + return _client_ssl_defaults + ssl_ctx = ssl.create_default_context( + ssl.Purpose.SERVER_AUTH, cafile=self.request.ca_certs + ) + if not self.request.validate_cert: + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + if self.request.client_cert is not None: + ssl_ctx.load_cert_chain( + self.request.client_cert, self.request.client_key + ) + if hasattr(ssl, "OP_NO_COMPRESSION"): + # See netutil.ssl_options_to_context + ssl_ctx.options |= ssl.OP_NO_COMPRESSION + return ssl_ctx + return None + + def _on_timeout(self, info: str = None) -> None: + """Timeout callback of _HTTPConnection instance. + + Raise a `HTTPTimeoutError` when a timeout occurs. + + :info string key: More detailed timeout information. + """ + self._timeout = None + error_message = "Timeout {0}".format(info) if info else "Timeout" + if self.final_callback is not None: + self._handle_exception( + HTTPTimeoutError, HTTPTimeoutError(error_message), None + ) + + def _remove_timeout(self) -> None: + if self._timeout is not None: + self.io_loop.remove_timeout(self._timeout) + self._timeout = None + + def _create_connection(self, stream: IOStream) -> HTTP1Connection: + stream.set_nodelay(True) + connection = HTTP1Connection( + stream, + True, + HTTP1ConnectionParameters( + no_keep_alive=True, + max_header_size=self.max_header_size, + max_body_size=self.max_body_size, + decompress=bool(self.request.decompress_response), + ), + self._sockaddr, + ) + return connection + + async def _write_body(self, start_read: bool) -> None: + if self.request.body is not None: + self.connection.write(self.request.body) + elif self.request.body_producer is not None: + fut = self.request.body_producer(self.connection.write) + if fut is not None: + await fut + self.connection.finish() + if start_read: + try: + await self.connection.read_response(self) + except StreamClosedError: + if not self._handle_exception(*sys.exc_info()): + raise + + def _release(self) -> None: + if self.release_callback is not None: + release_callback = self.release_callback + self.release_callback = None # type: ignore + release_callback() + + def _run_callback(self, response: HTTPResponse) -> None: + self._release() + if self.final_callback is not None: + final_callback = self.final_callback + self.final_callback = None # type: ignore + self.io_loop.add_callback(final_callback, response) + + def _handle_exception( + self, + typ: "Optional[Type[BaseException]]", + value: Optional[BaseException], + tb: Optional[TracebackType], + ) -> bool: + if self.final_callback: + self._remove_timeout() + if isinstance(value, StreamClosedError): + if value.real_error is None: + value = HTTPStreamClosedError("Stream closed") + else: + value = value.real_error + self._run_callback( + HTTPResponse( + self.request, + 599, + error=value, + request_time=self.io_loop.time() - self.start_time, + start_time=self.start_wall_time, + ) + ) + + if hasattr(self, "stream"): + # TODO: this may cause a StreamClosedError to be raised + # by the connection's Future. Should we cancel the + # connection more gracefully? + self.stream.close() + return True + else: + # If our callback has already been called, we are probably + # catching an exception that is not caused by us but rather + # some child of our callback. Rather than drop it on the floor, + # pass it along, unless it's just the stream being closed. + return isinstance(value, StreamClosedError) + + def on_connection_close(self) -> None: + if self.final_callback is not None: + message = "Connection closed" + if self.stream.error: + raise self.stream.error + try: + raise HTTPStreamClosedError(message) + except HTTPStreamClosedError: + self._handle_exception(*sys.exc_info()) + + async def headers_received( + self, + first_line: Union[httputil.ResponseStartLine, httputil.RequestStartLine], + headers: httputil.HTTPHeaders, + ) -> None: + assert isinstance(first_line, httputil.ResponseStartLine) + if self.request.expect_100_continue and first_line.code == 100: + await self._write_body(False) + return + self.code = first_line.code + self.reason = first_line.reason + self.headers = headers + + if self._should_follow_redirect(): + return + + if self.request.header_callback is not None: + # Reassemble the start line. + self.request.header_callback("%s %s %s\r\n" % first_line) + for k, v in self.headers.get_all(): + self.request.header_callback("%s: %s\r\n" % (k, v)) + self.request.header_callback("\r\n") + + def _should_follow_redirect(self) -> bool: + if self.request.follow_redirects: + assert self.request.max_redirects is not None + return ( + self.code in (301, 302, 303, 307, 308) + and self.request.max_redirects > 0 + and self.headers is not None + and self.headers.get("Location") is not None + ) + return False + + def finish(self) -> None: + assert self.code is not None + data = b"".join(self.chunks) + self._remove_timeout() + original_request = getattr(self.request, "original_request", self.request) + if self._should_follow_redirect(): + assert isinstance(self.request, _RequestProxy) + new_request = copy.copy(self.request.request) + new_request.url = urllib.parse.urljoin( + self.request.url, self.headers["Location"] + ) + new_request.max_redirects = self.request.max_redirects - 1 + del new_request.headers["Host"] + # https://tools.ietf.org/html/rfc7231#section-6.4 + # + # The original HTTP spec said that after a 301 or 302 + # redirect, the request method should be preserved. + # However, browsers implemented this by changing the + # method to GET, and the behavior stuck. 303 redirects + # always specified this POST-to-GET behavior (arguably 303 + # redirects should change *all* requests to GET, but + # libcurl only does this for POST so we follow their + # example). + if self.code in (301, 302, 303) and self.request.method == "POST": + new_request.method = "GET" + new_request.body = None + for h in [ + "Content-Length", + "Content-Type", + "Content-Encoding", + "Transfer-Encoding", + ]: + try: + del self.request.headers[h] + except KeyError: + pass + new_request.original_request = original_request + final_callback = self.final_callback + self.final_callback = None + self._release() + fut = self.client.fetch(new_request, raise_error=False) + fut.add_done_callback(lambda f: final_callback(f.result())) + self._on_end_request() + return + if self.request.streaming_callback: + buffer = BytesIO() + else: + buffer = BytesIO(data) # TODO: don't require one big string? + response = HTTPResponse( + original_request, + self.code, + reason=getattr(self, "reason", None), + headers=self.headers, + request_time=self.io_loop.time() - self.start_time, + start_time=self.start_wall_time, + buffer=buffer, + effective_url=self.request.url, + ) + self._run_callback(response) + self._on_end_request() + + def _on_end_request(self) -> None: + self.stream.close() + + def data_received(self, chunk: bytes) -> None: + if self._should_follow_redirect(): + # We're going to follow a redirect so just discard the body. + return + if self.request.streaming_callback is not None: + self.request.streaming_callback(chunk) + else: + self.chunks.append(chunk) + + +if __name__ == "__main__": + AsyncHTTPClient.configure(SimpleAsyncHTTPClient) + main() diff --git a/venv/lib/python3.7/site-packages/tornado/tcpclient.py b/venv/lib/python3.7/site-packages/tornado/tcpclient.py new file mode 100644 index 0000000..9fe6971 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/tcpclient.py @@ -0,0 +1,334 @@ +# +# Copyright 2014 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. + +"""A non-blocking TCP connection factory. +""" + +import functools +import socket +import numbers +import datetime +import ssl + +from tornado.concurrent import Future, future_add_done_callback +from tornado.ioloop import IOLoop +from tornado.iostream import IOStream +from tornado import gen +from tornado.netutil import Resolver +from tornado.platform.auto import set_close_exec +from tornado.gen import TimeoutError + +import typing +from typing import Any, Union, Dict, Tuple, List, Callable, Iterator + +if typing.TYPE_CHECKING: + from typing import Optional, Set # noqa: F401 + +_INITIAL_CONNECT_TIMEOUT = 0.3 + + +class _Connector(object): + """A stateless implementation of the "Happy Eyeballs" algorithm. + + "Happy Eyeballs" is documented in RFC6555 as the recommended practice + for when both IPv4 and IPv6 addresses are available. + + In this implementation, we partition the addresses by family, and + make the first connection attempt to whichever address was + returned first by ``getaddrinfo``. If that connection fails or + times out, we begin a connection in parallel to the first address + of the other family. If there are additional failures we retry + with other addresses, keeping one connection attempt per family + in flight at a time. + + http://tools.ietf.org/html/rfc6555 + + """ + + def __init__( + self, + addrinfo: List[Tuple], + connect: Callable[ + [socket.AddressFamily, Tuple], Tuple[IOStream, "Future[IOStream]"] + ], + ) -> None: + self.io_loop = IOLoop.current() + self.connect = connect + + self.future = ( + Future() + ) # type: Future[Tuple[socket.AddressFamily, Any, IOStream]] + self.timeout = None # type: Optional[object] + self.connect_timeout = None # type: Optional[object] + self.last_error = None # type: Optional[Exception] + self.remaining = len(addrinfo) + self.primary_addrs, self.secondary_addrs = self.split(addrinfo) + self.streams = set() # type: Set[IOStream] + + @staticmethod + def split( + addrinfo: List[Tuple] + ) -> Tuple[ + List[Tuple[socket.AddressFamily, Tuple]], + List[Tuple[socket.AddressFamily, Tuple]], + ]: + """Partition the ``addrinfo`` list by address family. + + Returns two lists. The first list contains the first entry from + ``addrinfo`` and all others with the same family, and the + second list contains all other addresses (normally one list will + be AF_INET and the other AF_INET6, although non-standard resolvers + may return additional families). + """ + primary = [] + secondary = [] + primary_af = addrinfo[0][0] + for af, addr in addrinfo: + if af == primary_af: + primary.append((af, addr)) + else: + secondary.append((af, addr)) + return primary, secondary + + def start( + self, + timeout: float = _INITIAL_CONNECT_TIMEOUT, + connect_timeout: Union[float, datetime.timedelta] = None, + ) -> "Future[Tuple[socket.AddressFamily, Any, IOStream]]": + self.try_connect(iter(self.primary_addrs)) + self.set_timeout(timeout) + if connect_timeout is not None: + self.set_connect_timeout(connect_timeout) + return self.future + + def try_connect(self, addrs: Iterator[Tuple[socket.AddressFamily, Tuple]]) -> None: + try: + af, addr = next(addrs) + except StopIteration: + # We've reached the end of our queue, but the other queue + # might still be working. Send a final error on the future + # only when both queues are finished. + if self.remaining == 0 and not self.future.done(): + self.future.set_exception( + self.last_error or IOError("connection failed") + ) + return + stream, future = self.connect(af, addr) + self.streams.add(stream) + future_add_done_callback( + future, functools.partial(self.on_connect_done, addrs, af, addr) + ) + + def on_connect_done( + self, + addrs: Iterator[Tuple[socket.AddressFamily, Tuple]], + af: socket.AddressFamily, + addr: Tuple, + future: "Future[IOStream]", + ) -> None: + self.remaining -= 1 + try: + stream = future.result() + except Exception as e: + if self.future.done(): + return + # Error: try again (but remember what happened so we have an + # error to raise in the end) + self.last_error = e + self.try_connect(addrs) + if self.timeout is not None: + # If the first attempt failed, don't wait for the + # timeout to try an address from the secondary queue. + self.io_loop.remove_timeout(self.timeout) + self.on_timeout() + return + self.clear_timeouts() + if self.future.done(): + # This is a late arrival; just drop it. + stream.close() + else: + self.streams.discard(stream) + self.future.set_result((af, addr, stream)) + self.close_streams() + + def set_timeout(self, timeout: float) -> None: + self.timeout = self.io_loop.add_timeout( + self.io_loop.time() + timeout, self.on_timeout + ) + + def on_timeout(self) -> None: + self.timeout = None + if not self.future.done(): + self.try_connect(iter(self.secondary_addrs)) + + def clear_timeout(self) -> None: + if self.timeout is not None: + self.io_loop.remove_timeout(self.timeout) + + def set_connect_timeout( + self, connect_timeout: Union[float, datetime.timedelta] + ) -> None: + self.connect_timeout = self.io_loop.add_timeout( + connect_timeout, self.on_connect_timeout + ) + + def on_connect_timeout(self) -> None: + if not self.future.done(): + self.future.set_exception(TimeoutError()) + self.close_streams() + + def clear_timeouts(self) -> None: + if self.timeout is not None: + self.io_loop.remove_timeout(self.timeout) + if self.connect_timeout is not None: + self.io_loop.remove_timeout(self.connect_timeout) + + def close_streams(self) -> None: + for stream in self.streams: + stream.close() + + +class TCPClient(object): + """A non-blocking TCP connection factory. + + .. versionchanged:: 5.0 + The ``io_loop`` argument (deprecated since version 4.1) has been removed. + """ + + def __init__(self, resolver: Resolver = None) -> None: + if resolver is not None: + self.resolver = resolver + self._own_resolver = False + else: + self.resolver = Resolver() + self._own_resolver = True + + def close(self) -> None: + if self._own_resolver: + self.resolver.close() + + async def connect( + self, + host: str, + port: int, + af: socket.AddressFamily = socket.AF_UNSPEC, + ssl_options: Union[Dict[str, Any], ssl.SSLContext] = None, + max_buffer_size: int = None, + source_ip: str = None, + source_port: int = None, + timeout: Union[float, datetime.timedelta] = None, + ) -> IOStream: + """Connect to the given host and port. + + Asynchronously returns an `.IOStream` (or `.SSLIOStream` if + ``ssl_options`` is not None). + + Using the ``source_ip`` kwarg, one can specify the source + IP address to use when establishing the connection. + In case the user needs to resolve and + use a specific interface, it has to be handled outside + of Tornado as this depends very much on the platform. + + Raises `TimeoutError` if the input future does not complete before + ``timeout``, which may be specified in any form allowed by + `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or an absolute time + relative to `.IOLoop.time`) + + Similarly, when the user requires a certain source port, it can + be specified using the ``source_port`` arg. + + .. versionchanged:: 4.5 + Added the ``source_ip`` and ``source_port`` arguments. + + .. versionchanged:: 5.0 + Added the ``timeout`` argument. + """ + if timeout is not None: + if isinstance(timeout, numbers.Real): + timeout = IOLoop.current().time() + timeout + elif isinstance(timeout, datetime.timedelta): + timeout = IOLoop.current().time() + timeout.total_seconds() + else: + raise TypeError("Unsupported timeout %r" % timeout) + if timeout is not None: + addrinfo = await gen.with_timeout( + timeout, self.resolver.resolve(host, port, af) + ) + else: + addrinfo = await self.resolver.resolve(host, port, af) + connector = _Connector( + addrinfo, + functools.partial( + self._create_stream, + max_buffer_size, + source_ip=source_ip, + source_port=source_port, + ), + ) + af, addr, stream = await connector.start(connect_timeout=timeout) + # TODO: For better performance we could cache the (af, addr) + # information here and re-use it on subsequent connections to + # the same host. (http://tools.ietf.org/html/rfc6555#section-4.2) + if ssl_options is not None: + if timeout is not None: + stream = await gen.with_timeout( + timeout, + stream.start_tls( + False, ssl_options=ssl_options, server_hostname=host + ), + ) + else: + stream = await stream.start_tls( + False, ssl_options=ssl_options, server_hostname=host + ) + return stream + + def _create_stream( + self, + max_buffer_size: int, + af: socket.AddressFamily, + addr: Tuple, + source_ip: str = None, + source_port: int = None, + ) -> Tuple[IOStream, "Future[IOStream]"]: + # Always connect in plaintext; we'll convert to ssl if necessary + # after one connection has completed. + source_port_bind = source_port if isinstance(source_port, int) else 0 + source_ip_bind = source_ip + if source_port_bind and not source_ip: + # User required a specific port, but did not specify + # a certain source IP, will bind to the default loopback. + source_ip_bind = "::1" if af == socket.AF_INET6 else "127.0.0.1" + # Trying to use the same address family as the requested af socket: + # - 127.0.0.1 for IPv4 + # - ::1 for IPv6 + socket_obj = socket.socket(af) + set_close_exec(socket_obj.fileno()) + if source_port_bind or source_ip_bind: + # If the user requires binding also to a specific IP/port. + try: + socket_obj.bind((source_ip_bind, source_port_bind)) + except socket.error: + socket_obj.close() + # Fail loudly if unable to use the IP/port. + raise + try: + stream = IOStream(socket_obj, max_buffer_size=max_buffer_size) + except socket.error as e: + fu = Future() # type: Future[IOStream] + fu.set_exception(e) + return stream, fu + else: + return stream, stream.connect(addr) diff --git a/venv/lib/python3.7/site-packages/tornado/tcpserver.py b/venv/lib/python3.7/site-packages/tornado/tcpserver.py new file mode 100644 index 0000000..eaf08b2 --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/tcpserver.py @@ -0,0 +1,330 @@ +# +# Copyright 2011 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. + +"""A non-blocking, single-threaded TCP server.""" + +import errno +import os +import socket +import ssl + +from tornado import gen +from tornado.log import app_log +from tornado.ioloop import IOLoop +from tornado.iostream import IOStream, SSLIOStream +from tornado.netutil import bind_sockets, add_accept_handler, ssl_wrap_socket +from tornado import process +from tornado.util import errno_from_exception + +import typing +from typing import Union, Dict, Any, Iterable, Optional, Awaitable + +if typing.TYPE_CHECKING: + from typing import Callable, List # noqa: F401 + + +class TCPServer(object): + r"""A non-blocking, single-threaded TCP server. + + To use `TCPServer`, define a subclass which overrides the `handle_stream` + method. For example, a simple echo server could be defined like this:: + + from tornado.tcpserver import TCPServer + from tornado.iostream import StreamClosedError + from tornado import gen + + class EchoServer(TCPServer): + async def handle_stream(self, stream, address): + while True: + try: + data = await stream.read_until(b"\n") + await stream.write(data) + except StreamClosedError: + break + + To make this server serve SSL traffic, send the ``ssl_options`` keyword + argument with an `ssl.SSLContext` object. For compatibility with older + versions of Python ``ssl_options`` may also be a dictionary of keyword + arguments for the `ssl.wrap_socket` method.:: + + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"), + os.path.join(data_dir, "mydomain.key")) + TCPServer(ssl_options=ssl_ctx) + + `TCPServer` initialization follows one of three patterns: + + 1. `listen`: simple single-process:: + + server = TCPServer() + server.listen(8888) + IOLoop.current().start() + + 2. `bind`/`start`: simple multi-process:: + + server = TCPServer() + server.bind(8888) + server.start(0) # Forks multiple sub-processes + IOLoop.current().start() + + When using this interface, an `.IOLoop` must *not* be passed + to the `TCPServer` constructor. `start` will always start + the server on the default singleton `.IOLoop`. + + 3. `add_sockets`: advanced multi-process:: + + sockets = bind_sockets(8888) + tornado.process.fork_processes(0) + server = TCPServer() + server.add_sockets(sockets) + IOLoop.current().start() + + The `add_sockets` interface is more complicated, but it can be + used with `tornado.process.fork_processes` to give you more + flexibility in when the fork happens. `add_sockets` can + also be used in single-process servers if you want to create + your listening sockets in some way other than + `~tornado.netutil.bind_sockets`. + + .. versionadded:: 3.1 + The ``max_buffer_size`` argument. + + .. versionchanged:: 5.0 + The ``io_loop`` argument has been removed. + """ + + def __init__( + self, + ssl_options: Union[Dict[str, Any], ssl.SSLContext] = None, + max_buffer_size: int = None, + read_chunk_size: int = None, + ) -> None: + self.ssl_options = ssl_options + self._sockets = {} # type: Dict[int, socket.socket] + self._handlers = {} # type: Dict[int, Callable[[], None]] + self._pending_sockets = [] # type: List[socket.socket] + self._started = False + self._stopped = False + self.max_buffer_size = max_buffer_size + self.read_chunk_size = read_chunk_size + + # Verify the SSL options. Otherwise we don't get errors until clients + # connect. This doesn't verify that the keys are legitimate, but + # the SSL module doesn't do that until there is a connected socket + # which seems like too much work + if self.ssl_options is not None and isinstance(self.ssl_options, dict): + # Only certfile is required: it can contain both keys + if "certfile" not in self.ssl_options: + raise KeyError('missing key "certfile" in ssl_options') + + if not os.path.exists(self.ssl_options["certfile"]): + raise ValueError( + 'certfile "%s" does not exist' % self.ssl_options["certfile"] + ) + if "keyfile" in self.ssl_options and not os.path.exists( + self.ssl_options["keyfile"] + ): + raise ValueError( + 'keyfile "%s" does not exist' % self.ssl_options["keyfile"] + ) + + def listen(self, port: int, address: str = "") -> None: + """Starts accepting connections on the given port. + + This method may be called more than once to listen on multiple ports. + `listen` takes effect immediately; it is not necessary to call + `TCPServer.start` afterwards. It is, however, necessary to start + the `.IOLoop`. + """ + sockets = bind_sockets(port, address=address) + self.add_sockets(sockets) + + def add_sockets(self, sockets: Iterable[socket.socket]) -> None: + """Makes this server start accepting connections on the given sockets. + + The ``sockets`` parameter is a list of socket objects such as + those returned by `~tornado.netutil.bind_sockets`. + `add_sockets` is typically used in combination with that + method and `tornado.process.fork_processes` to provide greater + control over the initialization of a multi-process server. + """ + for sock in sockets: + self._sockets[sock.fileno()] = sock + self._handlers[sock.fileno()] = add_accept_handler( + sock, self._handle_connection + ) + + def add_socket(self, socket: socket.socket) -> None: + """Singular version of `add_sockets`. Takes a single socket object.""" + self.add_sockets([socket]) + + def bind( + self, + port: int, + address: str = None, + family: socket.AddressFamily = socket.AF_UNSPEC, + backlog: int = 128, + reuse_port: bool = False, + ) -> None: + """Binds this server to the given port on the given address. + + To start the server, call `start`. If you want to run this server + in a single process, you can call `listen` as a shortcut to the + sequence of `bind` and `start` calls. + + Address may be either an IP address or hostname. If it's a hostname, + the server will listen on all IP addresses associated with the + name. Address may be an empty string or None to listen on all + available interfaces. Family may be set to either `socket.AF_INET` + or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise + both will be used if available. + + The ``backlog`` argument has the same meaning as for + `socket.listen <socket.socket.listen>`. The ``reuse_port`` argument + has the same meaning as for `.bind_sockets`. + + This method may be called multiple times prior to `start` to listen + on multiple ports or interfaces. + + .. versionchanged:: 4.4 + Added the ``reuse_port`` argument. + """ + sockets = bind_sockets( + port, address=address, family=family, backlog=backlog, reuse_port=reuse_port + ) + if self._started: + self.add_sockets(sockets) + else: + self._pending_sockets.extend(sockets) + + def start(self, num_processes: Optional[int] = 1, max_restarts: int = None) -> None: + """Starts this server in the `.IOLoop`. + + By default, we run the server in this process and do not fork any + additional child process. + + If num_processes is ``None`` or <= 0, we detect the number of cores + available on this machine and fork that number of child + processes. If num_processes is given and > 1, we fork that + specific number of sub-processes. + + Since we use processes and not threads, there is no shared memory + between any server code. + + Note that multiple processes are not compatible with the autoreload + module (or the ``autoreload=True`` option to `tornado.web.Application` + which defaults to True when ``debug=True``). + When using multiple processes, no IOLoops can be created or + referenced until after the call to ``TCPServer.start(n)``. + + The ``max_restarts`` argument is passed to `.fork_processes`. + + .. versionchanged:: 6.0 + + Added ``max_restarts`` argument. + """ + assert not self._started + self._started = True + if num_processes != 1: + process.fork_processes(num_processes, max_restarts) + sockets = self._pending_sockets + self._pending_sockets = [] + self.add_sockets(sockets) + + def stop(self) -> None: + """Stops listening for new connections. + + Requests currently in progress may still continue after the + server is stopped. + """ + if self._stopped: + return + self._stopped = True + for fd, sock in self._sockets.items(): + assert sock.fileno() == fd + # Unregister socket from IOLoop + self._handlers.pop(fd)() + sock.close() + + def handle_stream( + self, stream: IOStream, address: tuple + ) -> Optional[Awaitable[None]]: + """Override to handle a new `.IOStream` from an incoming connection. + + This method may be a coroutine; if so any exceptions it raises + asynchronously will be logged. Accepting of incoming connections + will not be blocked by this coroutine. + + If this `TCPServer` is configured for SSL, ``handle_stream`` + may be called before the SSL handshake has completed. Use + `.SSLIOStream.wait_for_handshake` if you need to verify the client's + certificate or use NPN/ALPN. + + .. versionchanged:: 4.2 + Added the option for this method to be a coroutine. + """ + raise NotImplementedError() + + def _handle_connection(self, connection: socket.socket, address: Any) -> None: + if self.ssl_options is not None: + assert ssl, "Python 2.6+ and OpenSSL required for SSL" + try: + connection = ssl_wrap_socket( + connection, + self.ssl_options, + server_side=True, + do_handshake_on_connect=False, + ) + except ssl.SSLError as err: + if err.args[0] == ssl.SSL_ERROR_EOF: + return connection.close() + else: + raise + except socket.error as err: + # If the connection is closed immediately after it is created + # (as in a port scan), we can get one of several errors. + # wrap_socket makes an internal call to getpeername, + # which may return either EINVAL (Mac OS X) or ENOTCONN + # (Linux). If it returns ENOTCONN, this error is + # silently swallowed by the ssl module, so we need to + # catch another error later on (AttributeError in + # SSLIOStream._do_ssl_handshake). + # To test this behavior, try nmap with the -sT flag. + # https://github.com/tornadoweb/tornado/pull/750 + if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL): + return connection.close() + else: + raise + try: + if self.ssl_options is not None: + stream = SSLIOStream( + connection, + max_buffer_size=self.max_buffer_size, + read_chunk_size=self.read_chunk_size, + ) # type: IOStream + else: + stream = IOStream( + connection, + max_buffer_size=self.max_buffer_size, + read_chunk_size=self.read_chunk_size, + ) + + future = self.handle_stream(stream, address) + if future is not None: + IOLoop.current().add_future( + gen.convert_yielded(future), lambda f: f.result() + ) + except Exception: + app_log.error("Error in connection callback", exc_info=True) diff --git a/venv/lib/python3.7/site-packages/tornado/template.py b/venv/lib/python3.7/site-packages/tornado/template.py new file mode 100644 index 0000000..1433dfa --- /dev/null +++ b/venv/lib/python3.7/site-packages/tornado/template.py @@ -0,0 +1,1043 @@ +# +# Copyright 2009 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. + +"""A simple template system that compiles templates to Python code. + +Basic usage looks like:: + + t = template.Template("<html>{{ myvalue }}</html>") + print(t.generate(myvalue="XXX")) + +`Loader` is a class that loads templates from a root directory and caches +the compiled templates:: + + loader = template.Loader("/home/btaylor") + print(loader.load("test.html").generate(myvalue="XXX")) + +We compile all templates to raw Python. Error-reporting is currently... uh, +interesting. Syntax for the templates:: + + ### base.html + <html> + <head> + <title>{% block title %}Default title{% end %} + + +
    + {% for student in students %} + {% block student %} +
  • {{ escape(student.name) }}
  • + {% end %} + {% end %} +
+ + + + ### bold.html + {% extends "base.html" %} + + {% block title %}A bolder title{% end %} + + {% block student %} +
  • {{ escape(student.name) }}
  • + {% end %} + +Unlike most other template systems, we do not put any restrictions on the +expressions you can include in your statements. ``if`` and ``for`` blocks get +translated exactly into Python, so you can do complex expressions like:: + + {% for student in [p for p in people if p.student and p.age > 23] %} +
  • {{ escape(student.name) }}
  • + {% end %} + +Translating directly to Python means you can apply functions to expressions +easily, like the ``escape()`` function in the examples above. You can pass +functions in to your template just like any other variable +(In a `.RequestHandler`, override `.RequestHandler.get_template_namespace`):: + + ### Python code + def add(x, y): + return x + y + template.execute(add=add) + + ### The template + {{ add(1, 2) }} + +We provide the functions `escape() <.xhtml_escape>`, `.url_escape()`, +`.json_encode()`, and `.squeeze()` to all templates by default. + +Typical applications do not create `Template` or `Loader` instances by +hand, but instead use the `~.RequestHandler.render` and +`~.RequestHandler.render_string` methods of +`tornado.web.RequestHandler`, which load templates automatically based +on the ``template_path`` `.Application` setting. + +Variable names beginning with ``_tt_`` are reserved by the template +system and should not be used by application code. + +Syntax Reference +---------------- + +Template expressions are surrounded by double curly braces: ``{{ ... }}``. +The contents may be any python expression, which will be escaped according +to the current autoescape setting and inserted into the output. Other +template directives use ``{% %}``. + +To comment out a section so that it is omitted from the output, surround it +with ``{# ... #}``. + +These tags may be escaped as ``{{!``, ``{%!``, and ``{#!`` +if you need to include a literal ``{{``, ``{%``, or ``{#`` in the output. + + +``{% apply *function* %}...{% end %}`` + Applies a function to the output of all template code between ``apply`` + and ``end``:: + + {% apply linkify %}{{name}} said: {{message}}{% end %} + + Note that as an implementation detail apply blocks are implemented + as nested functions and thus may interact strangely with variables + set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}`` + within loops. + +``{% autoescape *function* %}`` + Sets the autoescape mode for the current file. This does not affect + other files, even those referenced by ``{% include %}``. Note that + autoescaping can also be configured globally, at the `.Application` + or `Loader`.:: + + {% autoescape xhtml_escape %} + {% autoescape None %} + +``{% block *name* %}...{% end %}`` + Indicates a named, replaceable block for use with ``{% extends %}``. + Blocks in the parent template will be replaced with the contents of + the same-named block in a child template.:: + + + {% block title %}Default title{% end %} + + + {% extends "base.html" %} + {% block title %}My page title{% end %} + +``{% comment ... %}`` + A comment which will be removed from the template output. Note that + there is no ``{% end %}`` tag; the comment goes from the word ``comment`` + to the closing ``%}`` tag. + +``{% extends *filename* %}`` + Inherit from another template. Templates that use ``extends`` should + contain one or more ``block`` tags to replace content from the parent + template. Anything in the child template not contained in a ``block`` + tag will be ignored. For an example, see the ``{% block %}`` tag. + +``{% for *var* in *expr* %}...{% end %}`` + Same as the python ``for`` statement. ``{% break %}`` and + ``{% continue %}`` may be used inside the loop. + +``{% from *x* import *y* %}`` + Same as the python ``import`` statement. + +``{% if *condition* %}...{% elif *condition* %}...{% else %}...{% end %}`` + Conditional statement - outputs the first section whose condition is + true. (The ``elif`` and ``else`` sections are optional) + +``{% import *module* %}`` + Same as the python ``import`` statement. + +``{% include *filename* %}`` + Includes another template file. The included file can see all the local + variables as if it were copied directly to the point of the ``include`` + directive (the ``{% autoescape %}`` directive is an exception). + Alternately, ``{% module Template(filename, **kwargs) %}`` may be used + to include another template with an isolated namespace. + +``{% module *expr* %}`` + Renders a `~tornado.web.UIModule`. The output of the ``UIModule`` is + not escaped:: + + {% module Template("foo.html", arg=42) %} + + ``UIModules`` are a feature of the `tornado.web.RequestHandler` + class (and specifically its ``render`` method) and will not work + when the template system is used on its own in other contexts. + +``{% raw *expr* %}`` + Outputs the result of the given expression without autoescaping. + +``{% set *x* = *y* %}`` + Sets a local variable. + +``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}`` + Same as the python ``try`` statement. + +``{% while *condition* %}... {% end %}`` + Same as the python ``while`` statement. ``{% break %}`` and + ``{% continue %}`` may be used inside the loop. + +``{% whitespace *mode* %}`` + Sets the whitespace mode for the remainder of the current file + (or until the next ``{% whitespace %}`` directive). See + `filter_whitespace` for available options. New in Tornado 4.3. +""" + +import datetime +from io import StringIO +import linecache +import os.path +import posixpath +import re +import threading + +from tornado import escape +from tornado.log import app_log +from tornado.util import ObjectDict, exec_in, unicode_type + +from typing import Any, Union, Callable, List, Dict, Iterable, Optional, TextIO +import typing + +if typing.TYPE_CHECKING: + from typing import Tuple, ContextManager # noqa: F401 + +_DEFAULT_AUTOESCAPE = "xhtml_escape" + + +class _UnsetMarker: + pass + + +_UNSET = _UnsetMarker() + + +def filter_whitespace(mode: str, text: str) -> str: + """Transform whitespace in ``text`` according to ``mode``. + + Available modes are: + + * ``all``: Return all whitespace unmodified. + * ``single``: Collapse consecutive whitespace with a single whitespace + character, preserving newlines. + * ``oneline``: Collapse all runs of whitespace into a single space + character, removing all newlines in the process. + + .. versionadded:: 4.3 + """ + if mode == "all": + return text + elif mode == "single": + text = re.sub(r"([\t ]+)", " ", text) + text = re.sub(r"(\s*\n\s*)", "\n", text) + return text + elif mode == "oneline": + return re.sub(r"(\s+)", " ", text) + else: + raise Exception("invalid whitespace mode %s" % mode) + + +class Template(object): + """A compiled template. + + We compile into Python from the given template_string. You can generate + the template from variables with generate(). + """ + + # note that the constructor's signature is not extracted with + # autodoc because _UNSET looks like garbage. When changing + # this signature update website/sphinx/template.rst too. + def __init__( + self, + template_string: Union[str, bytes], + name: str = "", + loader: "BaseLoader" = None, + compress_whitespace: Union[bool, _UnsetMarker] = _UNSET, + autoescape: Union[str, _UnsetMarker] = _UNSET, + whitespace: str = None, + ) -> None: + """Construct a Template. + + :arg str template_string: the contents of the template file. + :arg str name: the filename from which the template was loaded + (used for error message). + :arg tornado.template.BaseLoader loader: the `~tornado.template.BaseLoader` responsible + for this template, used to resolve ``{% include %}`` and ``{% extend %}`` directives. + :arg bool compress_whitespace: Deprecated since Tornado 4.3. + Equivalent to ``whitespace="single"`` if true and + ``whitespace="all"`` if false. + :arg str autoescape: The name of a function in the template + namespace, or ``None`` to disable escaping by default. + :arg str whitespace: A string specifying treatment of whitespace; + see `filter_whitespace` for options. + + .. versionchanged:: 4.3 + Added ``whitespace`` parameter; deprecated ``compress_whitespace``. + """ + self.name = escape.native_str(name) + + if compress_whitespace is not _UNSET: + # Convert deprecated compress_whitespace (bool) to whitespace (str). + if whitespace is not None: + raise Exception("cannot set both whitespace and compress_whitespace") + whitespace = "single" if compress_whitespace else "all" + if whitespace is None: + if loader and loader.whitespace: + whitespace = loader.whitespace + else: + # Whitespace defaults by filename. + if name.endswith(".html") or name.endswith(".js"): + whitespace = "single" + else: + whitespace = "all" + # Validate the whitespace setting. + assert whitespace is not None + filter_whitespace(whitespace, "") + + if not isinstance(autoescape, _UnsetMarker): + self.autoescape = autoescape # type: Optional[str] + elif loader: + self.autoescape = loader.autoescape + else: + self.autoescape = _DEFAULT_AUTOESCAPE + + self.namespace = loader.namespace if loader else {} + reader = _TemplateReader(name, escape.native_str(template_string), whitespace) + self.file = _File(self, _parse(reader, self)) + self.code = self._generate_python(loader) + self.loader = loader + try: + # Under python2.5, the fake filename used here must match + # the module name used in __name__ below. + # The dont_inherit flag prevents template.py's future imports + # from being applied to the generated code. + self.compiled = compile( + escape.to_unicode(self.code), + "%s.generated.py" % self.name.replace(".", "_"), + "exec", + dont_inherit=True, + ) + except Exception: + formatted_code = _format_code(self.code).rstrip() + app_log.error("%s code:\n%s", self.name, formatted_code) + raise + + def generate(self, **kwargs: Any) -> bytes: + """Generate this template with the given arguments.""" + namespace = { + "escape": escape.xhtml_escape, + "xhtml_escape": escape.xhtml_escape, + "url_escape": escape.url_escape, + "json_encode": escape.json_encode, + "squeeze": escape.squeeze, + "linkify": escape.linkify, + "datetime": datetime, + "_tt_utf8": escape.utf8, # for internal use + "_tt_string_types": (unicode_type, bytes), + # __name__ and __loader__ allow the traceback mechanism to find + # the generated source code. + "__name__": self.name.replace(".", "_"), + "__loader__": ObjectDict(get_source=lambda name: self.code), + } + namespace.update(self.namespace) + namespace.update(kwargs) + exec_in(self.compiled, namespace) + execute = typing.cast(Callable[[], bytes], namespace["_tt_execute"]) + # Clear the traceback module's cache of source data now that + # we've generated a new template (mainly for this module's + # unittests, where different tests reuse the same name). + linecache.clearcache() + return execute() + + def _generate_python(self, loader: Optional["BaseLoader"]) -> str: + buffer = StringIO() + try: + # named_blocks maps from names to _NamedBlock objects + named_blocks = {} # type: Dict[str, _NamedBlock] + ancestors = self._get_ancestors(loader) + ancestors.reverse() + for ancestor in ancestors: + ancestor.find_named_blocks(loader, named_blocks) + writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template) + ancestors[0].generate(writer) + return buffer.getvalue() + finally: + buffer.close() + + def _get_ancestors(self, loader: Optional["BaseLoader"]) -> List["_File"]: + ancestors = [self.file] + for chunk in self.file.body.chunks: + if isinstance(chunk, _ExtendsBlock): + if not loader: + raise ParseError( + "{% extends %} block found, but no " "template loader" + ) + template = loader.load(chunk.name, self.name) + ancestors.extend(template._get_ancestors(loader)) + return ancestors + + +class BaseLoader(object): + """Base class for template loaders. + + You must use a template loader to use template constructs like + ``{% extends %}`` and ``{% include %}``. The loader caches all + templates after they are loaded the first time. + """ + + def __init__( + self, + autoescape: str = _DEFAULT_AUTOESCAPE, + namespace: Dict[str, Any] = None, + whitespace: str = None, + ) -> None: + """Construct a template loader. + + :arg str autoescape: The name of a function in the template + namespace, such as "xhtml_escape", or ``None`` to disable + autoescaping by default. + :arg dict namespace: A dictionary to be added to the default template + namespace, or ``None``. + :arg str whitespace: A string specifying default behavior for + whitespace in templates; see `filter_whitespace` for options. + Default is "single" for files ending in ".html" and ".js" and + "all" for other files. + + .. versionchanged:: 4.3 + Added ``whitespace`` parameter. + """ + self.autoescape = autoescape + self.namespace = namespace or {} + self.whitespace = whitespace + self.templates = {} # type: Dict[str, Template] + # self.lock protects self.templates. It's a reentrant lock + # because templates may load other templates via `include` or + # `extends`. Note that thanks to the GIL this code would be safe + # even without the lock, but could lead to wasted work as multiple + # threads tried to compile the same template simultaneously. + self.lock = threading.RLock() + + def reset(self) -> None: + """Resets the cache of compiled templates.""" + with self.lock: + self.templates = {} + + def resolve_path(self, name: str, parent_path: str = None) -> str: + """Converts a possibly-relative path to absolute (used internally).""" + raise NotImplementedError() + + def load(self, name: str, parent_path: str = None) -> Template: + """Loads a template.""" + name = self.resolve_path(name, parent_path=parent_path) + with self.lock: + if name not in self.templates: + self.templates[name] = self._create_template(name) + return self.templates[name] + + def _create_template(self, name: str) -> Template: + raise NotImplementedError() + + +class Loader(BaseLoader): + """A template loader that loads from a single root directory. + """ + + def __init__(self, root_directory: str, **kwargs: Any) -> None: + super(Loader, self).__init__(**kwargs) + self.root = os.path.abspath(root_directory) + + def resolve_path(self, name: str, parent_path: str = None) -> str: + if ( + parent_path + and not parent_path.startswith("<") + and not parent_path.startswith("/") + and not name.startswith("/") + ): + current_path = os.path.join(self.root, parent_path) + file_dir = os.path.dirname(os.path.abspath(current_path)) + relative_path = os.path.abspath(os.path.join(file_dir, name)) + if relative_path.startswith(self.root): + name = relative_path[len(self.root) + 1 :] + return name + + def _create_template(self, name: str) -> Template: + path = os.path.join(self.root, name) + with open(path, "rb") as f: + template = Template(f.read(), name=name, loader=self) + return template + + +class DictLoader(BaseLoader): + """A template loader that loads from a dictionary.""" + + def __init__(self, dict: Dict[str, str], **kwargs: Any) -> None: + super(DictLoader, self).__init__(**kwargs) + self.dict = dict + + def resolve_path(self, name: str, parent_path: str = None) -> str: + if ( + parent_path + and not parent_path.startswith("<") + and not parent_path.startswith("/") + and not name.startswith("/") + ): + file_dir = posixpath.dirname(parent_path) + name = posixpath.normpath(posixpath.join(file_dir, name)) + return name + + def _create_template(self, name: str) -> Template: + return Template(self.dict[name], name=name, loader=self) + + +class _Node(object): + def each_child(self) -> Iterable["_Node"]: + return () + + def generate(self, writer: "_CodeWriter") -> None: + raise NotImplementedError() + + def find_named_blocks( + self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"] + ) -> None: + for child in self.each_child(): + child.find_named_blocks(loader, named_blocks) + + +class _File(_Node): + def __init__(self, template: Template, body: "_ChunkList") -> None: + self.template = template + self.body = body + self.line = 0 + + def generate(self, writer: "_CodeWriter") -> None: + writer.write_line("def _tt_execute():", self.line) + with writer.indent(): + writer.write_line("_tt_buffer = []", self.line) + writer.write_line("_tt_append = _tt_buffer.append", self.line) + self.body.generate(writer) + writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line) + + def each_child(self) -> Iterable["_Node"]: + return (self.body,) + + +class _ChunkList(_Node): + def __init__(self, chunks: List[_Node]) -> None: + self.chunks = chunks + + def generate(self, writer: "_CodeWriter") -> None: + for chunk in self.chunks: + chunk.generate(writer) + + def each_child(self) -> Iterable["_Node"]: + return self.chunks + + +class _NamedBlock(_Node): + def __init__(self, name: str, body: _Node, template: Template, line: int) -> None: + self.name = name + self.body = body + self.template = template + self.line = line + + def each_child(self) -> Iterable["_Node"]: + return (self.body,) + + def generate(self, writer: "_CodeWriter") -> None: + block = writer.named_blocks[self.name] + with writer.include(block.template, self.line): + block.body.generate(writer) + + def find_named_blocks( + self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"] + ) -> None: + named_blocks[self.name] = self + _Node.find_named_blocks(self, loader, named_blocks) + + +class _ExtendsBlock(_Node): + def __init__(self, name: str) -> None: + self.name = name + + +class _IncludeBlock(_Node): + def __init__(self, name: str, reader: "_TemplateReader", line: int) -> None: + self.name = name + self.template_name = reader.name + self.line = line + + def find_named_blocks( + self, loader: Optional[BaseLoader], named_blocks: Dict[str, _NamedBlock] + ) -> None: + assert loader is not None + included = loader.load(self.name, self.template_name) + included.file.find_named_blocks(loader, named_blocks) + + def generate(self, writer: "_CodeWriter") -> None: + assert writer.loader is not None + included = writer.loader.load(self.name, self.template_name) + with writer.include(included, self.line): + included.file.body.generate(writer) + + +class _ApplyBlock(_Node): + def __init__(self, method: str, line: int, body: _Node) -> None: + self.method = method + self.line = line + self.body = body + + def each_child(self) -> Iterable["_Node"]: + return (self.body,) + + def generate(self, writer: "_CodeWriter") -> None: + method_name = "_tt_apply%d" % writer.apply_counter + writer.apply_counter += 1 + writer.write_line("def %s():" % method_name, self.line) + with writer.indent(): + writer.write_line("_tt_buffer = []", self.line) + writer.write_line("_tt_append = _tt_buffer.append", self.line) + self.body.generate(writer) + writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line) + writer.write_line( + "_tt_append(_tt_utf8(%s(%s())))" % (self.method, method_name), self.line + ) + + +class _ControlBlock(_Node): + def __init__(self, statement: str, line: int, body: _Node) -> None: + self.statement = statement + self.line = line + self.body = body + + def each_child(self) -> Iterable[_Node]: + return (self.body,) + + def generate(self, writer: "_CodeWriter") -> None: + writer.write_line("%s:" % self.statement, self.line) + with writer.indent(): + self.body.generate(writer) + # Just in case the body was empty + writer.write_line("pass", self.line) + + +class _IntermediateControlBlock(_Node): + def __init__(self, statement: str, line: int) -> None: + self.statement = statement + self.line = line + + def generate(self, writer: "_CodeWriter") -> None: + # In case the previous block was empty + writer.write_line("pass", self.line) + writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1) + + +class _Statement(_Node): + def __init__(self, statement: str, line: int) -> None: + self.statement = statement + self.line = line + + def generate(self, writer: "_CodeWriter") -> None: + writer.write_line(self.statement, self.line) + + +class _Expression(_Node): + def __init__(self, expression: str, line: int, raw: bool = False) -> None: + self.expression = expression + self.line = line + self.raw = raw + + def generate(self, writer: "_CodeWriter") -> None: + writer.write_line("_tt_tmp = %s" % self.expression, self.line) + writer.write_line( + "if isinstance(_tt_tmp, _tt_string_types):" " _tt_tmp = _tt_utf8(_tt_tmp)", + self.line, + ) + writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line) + if not self.raw and writer.current_template.autoescape is not None: + # In python3 functions like xhtml_escape return unicode, + # so we have to convert to utf8 again. + writer.write_line( + "_tt_tmp = _tt_utf8(%s(_tt_tmp))" % writer.current_template.autoescape, + self.line, + ) + writer.write_line("_tt_append(_tt_tmp)", self.line) + + +class _Module(_Expression): + def __init__(self, expression: str, line: int) -> None: + super(_Module, self).__init__("_tt_modules." + expression, line, raw=True) + + +class _Text(_Node): + def __init__(self, value: str, line: int, whitespace: str) -> None: + self.value = value + self.line = line + self.whitespace = whitespace + + def generate(self, writer: "_CodeWriter") -> None: + value = self.value + + # Compress whitespace if requested, with a crude heuristic to avoid + # altering preformatted whitespace. + if "
    " not in value:
    +            value = filter_whitespace(self.whitespace, value)
    +
    +        if value:
    +            writer.write_line("_tt_append(%r)" % escape.utf8(value), self.line)
    +
    +
    +class ParseError(Exception):
    +    """Raised for template syntax errors.
    +
    +    ``ParseError`` instances have ``filename`` and ``lineno`` attributes
    +    indicating the position of the error.
    +
    +    .. versionchanged:: 4.3
    +       Added ``filename`` and ``lineno`` attributes.
    +    """
    +
    +    def __init__(self, message: str, filename: str = None, lineno: int = 0) -> None:
    +        self.message = message
    +        # The names "filename" and "lineno" are chosen for consistency
    +        # with python SyntaxError.
    +        self.filename = filename
    +        self.lineno = lineno
    +
    +    def __str__(self) -> str:
    +        return "%s at %s:%d" % (self.message, self.filename, self.lineno)
    +
    +
    +class _CodeWriter(object):
    +    def __init__(
    +        self,
    +        file: TextIO,
    +        named_blocks: Dict[str, _NamedBlock],
    +        loader: Optional[BaseLoader],
    +        current_template: Template,
    +    ) -> None:
    +        self.file = file
    +        self.named_blocks = named_blocks
    +        self.loader = loader
    +        self.current_template = current_template
    +        self.apply_counter = 0
    +        self.include_stack = []  # type: List[Tuple[Template, int]]
    +        self._indent = 0
    +
    +    def indent_size(self) -> int:
    +        return self._indent
    +
    +    def indent(self) -> "ContextManager":
    +        class Indenter(object):
    +            def __enter__(_) -> "_CodeWriter":
    +                self._indent += 1
    +                return self
    +
    +            def __exit__(_, *args: Any) -> None:
    +                assert self._indent > 0
    +                self._indent -= 1
    +
    +        return Indenter()
    +
    +    def include(self, template: Template, line: int) -> "ContextManager":
    +        self.include_stack.append((self.current_template, line))
    +        self.current_template = template
    +
    +        class IncludeTemplate(object):
    +            def __enter__(_) -> "_CodeWriter":
    +                return self
    +
    +            def __exit__(_, *args: Any) -> None:
    +                self.current_template = self.include_stack.pop()[0]
    +
    +        return IncludeTemplate()
    +
    +    def write_line(self, line: str, line_number: int, indent: int = None) -> None:
    +        if indent is None:
    +            indent = self._indent
    +        line_comment = "  # %s:%d" % (self.current_template.name, line_number)
    +        if self.include_stack:
    +            ancestors = [
    +                "%s:%d" % (tmpl.name, lineno) for (tmpl, lineno) in self.include_stack
    +            ]
    +            line_comment += " (via %s)" % ", ".join(reversed(ancestors))
    +        print("    " * indent + line + line_comment, file=self.file)
    +
    +
    +class _TemplateReader(object):
    +    def __init__(self, name: str, text: str, whitespace: str) -> None:
    +        self.name = name
    +        self.text = text
    +        self.whitespace = whitespace
    +        self.line = 1
    +        self.pos = 0
    +
    +    def find(self, needle: str, start: int = 0, end: int = None) -> int:
    +        assert start >= 0, start
    +        pos = self.pos
    +        start += pos
    +        if end is None:
    +            index = self.text.find(needle, start)
    +        else:
    +            end += pos
    +            assert end >= start
    +            index = self.text.find(needle, start, end)
    +        if index != -1:
    +            index -= pos
    +        return index
    +
    +    def consume(self, count: int = None) -> str:
    +        if count is None:
    +            count = len(self.text) - self.pos
    +        newpos = self.pos + count
    +        self.line += self.text.count("\n", self.pos, newpos)
    +        s = self.text[self.pos : newpos]
    +        self.pos = newpos
    +        return s
    +
    +    def remaining(self) -> int:
    +        return len(self.text) - self.pos
    +
    +    def __len__(self) -> int:
    +        return self.remaining()
    +
    +    def __getitem__(self, key: Union[int, slice]) -> str:
    +        if isinstance(key, slice):
    +            size = len(self)
    +            start, stop, step = key.indices(size)
    +            if start is None:
    +                start = self.pos
    +            else:
    +                start += self.pos
    +            if stop is not None:
    +                stop += self.pos
    +            return self.text[slice(start, stop, step)]
    +        elif key < 0:
    +            return self.text[key]
    +        else:
    +            return self.text[self.pos + key]
    +
    +    def __str__(self) -> str:
    +        return self.text[self.pos :]
    +
    +    def raise_parse_error(self, msg: str) -> None:
    +        raise ParseError(msg, self.name, self.line)
    +
    +
    +def _format_code(code: str) -> str:
    +    lines = code.splitlines()
    +    format = "%%%dd  %%s\n" % len(repr(len(lines) + 1))
    +    return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
    +
    +
    +def _parse(
    +    reader: _TemplateReader,
    +    template: Template,
    +    in_block: str = None,
    +    in_loop: str = None,
    +) -> _ChunkList:
    +    body = _ChunkList([])
    +    while True:
    +        # Find next template directive
    +        curly = 0
    +        while True:
    +            curly = reader.find("{", curly)
    +            if curly == -1 or curly + 1 == reader.remaining():
    +                # EOF
    +                if in_block:
    +                    reader.raise_parse_error(
    +                        "Missing {%% end %%} block for %s" % in_block
    +                    )
    +                body.chunks.append(
    +                    _Text(reader.consume(), reader.line, reader.whitespace)
    +                )
    +                return body
    +            # If the first curly brace is not the start of a special token,
    +            # start searching from the character after it
    +            if reader[curly + 1] not in ("{", "%", "#"):
    +                curly += 1
    +                continue
    +            # When there are more than 2 curlies in a row, use the
    +            # innermost ones.  This is useful when generating languages
    +            # like latex where curlies are also meaningful
    +            if (
    +                curly + 2 < reader.remaining()
    +                and reader[curly + 1] == "{"
    +                and reader[curly + 2] == "{"
    +            ):
    +                curly += 1
    +                continue
    +            break
    +
    +        # Append any text before the special token
    +        if curly > 0:
    +            cons = reader.consume(curly)
    +            body.chunks.append(_Text(cons, reader.line, reader.whitespace))
    +
    +        start_brace = reader.consume(2)
    +        line = reader.line
    +
    +        # Template directives may be escaped as "{{!" or "{%!".
    +        # In this case output the braces and consume the "!".
    +        # This is especially useful in conjunction with jquery templates,
    +        # which also use double braces.
    +        if reader.remaining() and reader[0] == "!":
    +            reader.consume(1)
    +            body.chunks.append(_Text(start_brace, line, reader.whitespace))
    +            continue
    +
    +        # Comment
    +        if start_brace == "{#":
    +            end = reader.find("#}")
    +            if end == -1:
    +                reader.raise_parse_error("Missing end comment #}")
    +            contents = reader.consume(end).strip()
    +            reader.consume(2)
    +            continue
    +
    +        # Expression
    +        if start_brace == "{{":
    +            end = reader.find("}}")
    +            if end == -1:
    +                reader.raise_parse_error("Missing end expression }}")
    +            contents = reader.consume(end).strip()
    +            reader.consume(2)
    +            if not contents:
    +                reader.raise_parse_error("Empty expression")
    +            body.chunks.append(_Expression(contents, line))
    +            continue
    +
    +        # Block
    +        assert start_brace == "{%", start_brace
    +        end = reader.find("%}")
    +        if end == -1:
    +            reader.raise_parse_error("Missing end block %}")
    +        contents = reader.consume(end).strip()
    +        reader.consume(2)
    +        if not contents:
    +            reader.raise_parse_error("Empty block tag ({% %})")
    +
    +        operator, space, suffix = contents.partition(" ")
    +        suffix = suffix.strip()
    +
    +        # Intermediate ("else", "elif", etc) blocks
    +        intermediate_blocks = {
    +            "else": set(["if", "for", "while", "try"]),
    +            "elif": set(["if"]),
    +            "except": set(["try"]),
    +            "finally": set(["try"]),
    +        }
    +        allowed_parents = intermediate_blocks.get(operator)
    +        if allowed_parents is not None:
    +            if not in_block:
    +                reader.raise_parse_error(
    +                    "%s outside %s block" % (operator, allowed_parents)
    +                )
    +            if in_block not in allowed_parents:
    +                reader.raise_parse_error(
    +                    "%s block cannot be attached to %s block" % (operator, in_block)
    +                )
    +            body.chunks.append(_IntermediateControlBlock(contents, line))
    +            continue
    +
    +        # End tag
    +        elif operator == "end":
    +            if not in_block:
    +                reader.raise_parse_error("Extra {% end %} block")
    +            return body
    +
    +        elif operator in (
    +            "extends",
    +            "include",
    +            "set",
    +            "import",
    +            "from",
    +            "comment",
    +            "autoescape",
    +            "whitespace",
    +            "raw",
    +            "module",
    +        ):
    +            if operator == "comment":
    +                continue
    +            if operator == "extends":
    +                suffix = suffix.strip('"').strip("'")
    +                if not suffix:
    +                    reader.raise_parse_error("extends missing file path")
    +                block = _ExtendsBlock(suffix)  # type: _Node
    +            elif operator in ("import", "from"):
    +                if not suffix:
    +                    reader.raise_parse_error("import missing statement")
    +                block = _Statement(contents, line)
    +            elif operator == "include":
    +                suffix = suffix.strip('"').strip("'")
    +                if not suffix:
    +                    reader.raise_parse_error("include missing file path")
    +                block = _IncludeBlock(suffix, reader, line)
    +            elif operator == "set":
    +                if not suffix:
    +                    reader.raise_parse_error("set missing statement")
    +                block = _Statement(suffix, line)
    +            elif operator == "autoescape":
    +                fn = suffix.strip()  # type: Optional[str]
    +                if fn == "None":
    +                    fn = None
    +                template.autoescape = fn
    +                continue
    +            elif operator == "whitespace":
    +                mode = suffix.strip()
    +                # Validate the selected mode
    +                filter_whitespace(mode, "")
    +                reader.whitespace = mode
    +                continue
    +            elif operator == "raw":
    +                block = _Expression(suffix, line, raw=True)
    +            elif operator == "module":
    +                block = _Module(suffix, line)
    +            body.chunks.append(block)
    +            continue
    +
    +        elif operator in ("apply", "block", "try", "if", "for", "while"):
    +            # parse inner body recursively
    +            if operator in ("for", "while"):
    +                block_body = _parse(reader, template, operator, operator)
    +            elif operator == "apply":
    +                # apply creates a nested function so syntactically it's not
    +                # in the loop.
    +                block_body = _parse(reader, template, operator, None)
    +            else:
    +                block_body = _parse(reader, template, operator, in_loop)
    +
    +            if operator == "apply":
    +                if not suffix:
    +                    reader.raise_parse_error("apply missing method name")
    +                block = _ApplyBlock(suffix, line, block_body)
    +            elif operator == "block":
    +                if not suffix:
    +                    reader.raise_parse_error("block missing name")
    +                block = _NamedBlock(suffix, block_body, template, line)
    +            else:
    +                block = _ControlBlock(contents, line, block_body)
    +            body.chunks.append(block)
    +            continue
    +
    +        elif operator in ("break", "continue"):
    +            if not in_loop:
    +                reader.raise_parse_error(
    +                    "%s outside %s block" % (operator, set(["for", "while"]))
    +                )
    +            body.chunks.append(_Statement(contents, line))
    +            continue
    +
    +        else:
    +            reader.raise_parse_error("unknown operator: %r" % operator)
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/__init__.py b/venv/lib/python3.7/site-packages/tornado/test/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/__main__.py b/venv/lib/python3.7/site-packages/tornado/test/__main__.py
    new file mode 100644
    index 0000000..430c895
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/__main__.py
    @@ -0,0 +1,12 @@
    +"""Shim to allow python -m tornado.test.
    +
    +This only works in python 2.7+.
    +"""
    +from tornado.test.runtests import all, main
    +
    +# tornado.testing.main autodiscovery relies on 'all' being present in
    +# the main module, so import it here even though it is not used directly.
    +# The following line prevents a pyflakes warning.
    +all = all
    +
    +main()
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/asyncio_test.py b/venv/lib/python3.7/site-packages/tornado/test/asyncio_test.py
    new file mode 100644
    index 0000000..aa9c2f9
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/asyncio_test.py
    @@ -0,0 +1,190 @@
    +# 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 asyncio
    +import unittest
    +
    +from concurrent.futures import ThreadPoolExecutor
    +from tornado import gen
    +from tornado.ioloop import IOLoop
    +from tornado.platform.asyncio import (
    +    AsyncIOLoop,
    +    to_asyncio_future,
    +    AnyThreadEventLoopPolicy,
    +)
    +from tornado.testing import AsyncTestCase, gen_test
    +
    +
    +class AsyncIOLoopTest(AsyncTestCase):
    +    def get_new_ioloop(self):
    +        io_loop = AsyncIOLoop()
    +        return io_loop
    +
    +    def test_asyncio_callback(self):
    +        # Basic test that the asyncio loop is set up correctly.
    +        asyncio.get_event_loop().call_soon(self.stop)
    +        self.wait()
    +
    +    @gen_test
    +    def test_asyncio_future(self):
    +        # Test that we can yield an asyncio future from a tornado coroutine.
    +        # Without 'yield from', we must wrap coroutines in ensure_future,
    +        # which was introduced during Python 3.4, deprecating the prior "async".
    +        if hasattr(asyncio, "ensure_future"):
    +            ensure_future = asyncio.ensure_future
    +        else:
    +            # async is a reserved word in Python 3.7
    +            ensure_future = getattr(asyncio, "async")
    +
    +        x = yield ensure_future(
    +            asyncio.get_event_loop().run_in_executor(None, lambda: 42)
    +        )
    +        self.assertEqual(x, 42)
    +
    +    @gen_test
    +    def test_asyncio_yield_from(self):
    +        @gen.coroutine
    +        def f():
    +            event_loop = asyncio.get_event_loop()
    +            x = yield from event_loop.run_in_executor(None, lambda: 42)
    +            return x
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +
    +    def test_asyncio_adapter(self):
    +        # This test demonstrates that when using the asyncio coroutine
    +        # runner (i.e. run_until_complete), the to_asyncio_future
    +        # adapter is needed. No adapter is needed in the other direction,
    +        # as demonstrated by other tests in the package.
    +        @gen.coroutine
    +        def tornado_coroutine():
    +            yield gen.moment
    +            raise gen.Return(42)
    +
    +        async def native_coroutine_without_adapter():
    +            return await tornado_coroutine()
    +
    +        async def native_coroutine_with_adapter():
    +            return await to_asyncio_future(tornado_coroutine())
    +
    +        # Use the adapter, but two degrees from the tornado coroutine.
    +        async def native_coroutine_with_adapter2():
    +            return await to_asyncio_future(native_coroutine_without_adapter())
    +
    +        # Tornado supports native coroutines both with and without adapters
    +        self.assertEqual(self.io_loop.run_sync(native_coroutine_without_adapter), 42)
    +        self.assertEqual(self.io_loop.run_sync(native_coroutine_with_adapter), 42)
    +        self.assertEqual(self.io_loop.run_sync(native_coroutine_with_adapter2), 42)
    +
    +        # Asyncio only supports coroutines that yield asyncio-compatible
    +        # Futures (which our Future is since 5.0).
    +        self.assertEqual(
    +            asyncio.get_event_loop().run_until_complete(
    +                native_coroutine_without_adapter()
    +            ),
    +            42,
    +        )
    +        self.assertEqual(
    +            asyncio.get_event_loop().run_until_complete(
    +                native_coroutine_with_adapter()
    +            ),
    +            42,
    +        )
    +        self.assertEqual(
    +            asyncio.get_event_loop().run_until_complete(
    +                native_coroutine_with_adapter2()
    +            ),
    +            42,
    +        )
    +
    +
    +class LeakTest(unittest.TestCase):
    +    def setUp(self):
    +        # Trigger a cleanup of the mapping so we start with a clean slate.
    +        AsyncIOLoop().close()
    +        # If we don't clean up after ourselves other tests may fail on
    +        # py34.
    +        self.orig_policy = asyncio.get_event_loop_policy()
    +        asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
    +
    +    def tearDown(self):
    +        asyncio.get_event_loop().close()
    +        asyncio.set_event_loop_policy(self.orig_policy)
    +
    +    def test_ioloop_close_leak(self):
    +        orig_count = len(IOLoop._ioloop_for_asyncio)
    +        for i in range(10):
    +            # Create and close an AsyncIOLoop using Tornado interfaces.
    +            loop = AsyncIOLoop()
    +            loop.close()
    +        new_count = len(IOLoop._ioloop_for_asyncio) - orig_count
    +        self.assertEqual(new_count, 0)
    +
    +    def test_asyncio_close_leak(self):
    +        orig_count = len(IOLoop._ioloop_for_asyncio)
    +        for i in range(10):
    +            # Create and close an AsyncIOMainLoop using asyncio interfaces.
    +            loop = asyncio.new_event_loop()
    +            loop.call_soon(IOLoop.current)
    +            loop.call_soon(loop.stop)
    +            loop.run_forever()
    +            loop.close()
    +        new_count = len(IOLoop._ioloop_for_asyncio) - orig_count
    +        # Because the cleanup is run on new loop creation, we have one
    +        # dangling entry in the map (but only one).
    +        self.assertEqual(new_count, 1)
    +
    +
    +class AnyThreadEventLoopPolicyTest(unittest.TestCase):
    +    def setUp(self):
    +        self.orig_policy = asyncio.get_event_loop_policy()
    +        self.executor = ThreadPoolExecutor(1)
    +
    +    def tearDown(self):
    +        asyncio.set_event_loop_policy(self.orig_policy)
    +        self.executor.shutdown()
    +
    +    def get_event_loop_on_thread(self):
    +        def get_and_close_event_loop():
    +            """Get the event loop. Close it if one is returned.
    +
    +            Returns the (closed) event loop. This is a silly thing
    +            to do and leaves the thread in a broken state, but it's
    +            enough for this test. Closing the loop avoids resource
    +            leak warnings.
    +            """
    +            loop = asyncio.get_event_loop()
    +            loop.close()
    +            return loop
    +
    +        future = self.executor.submit(get_and_close_event_loop)
    +        return future.result()
    +
    +    def run_policy_test(self, accessor, expected_type):
    +        # With the default policy, non-main threads don't get an event
    +        # loop.
    +        self.assertRaises(
    +            (RuntimeError, AssertionError), self.executor.submit(accessor).result
    +        )
    +        # Set the policy and we can get a loop.
    +        asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
    +        self.assertIsInstance(self.executor.submit(accessor).result(), expected_type)
    +        # Clean up to silence leak warnings. Always use asyncio since
    +        # IOLoop doesn't (currently) close the underlying loop.
    +        self.executor.submit(lambda: asyncio.get_event_loop().close()).result()
    +
    +    def test_asyncio_accessor(self):
    +        self.run_policy_test(asyncio.get_event_loop, asyncio.AbstractEventLoop)
    +
    +    def test_tornado_accessor(self):
    +        self.run_policy_test(IOLoop.current, IOLoop)
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/auth_test.py b/venv/lib/python3.7/site-packages/tornado/test/auth_test.py
    new file mode 100644
    index 0000000..7807ae5
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/auth_test.py
    @@ -0,0 +1,609 @@
    +# These tests do not currently do much to verify the correct implementation
    +# of the openid/oauth protocols, they just exercise the major code paths
    +# and ensure that it doesn't blow up (e.g. with unicode/bytes issues in
    +# python 3)
    +
    +import unittest
    +
    +from tornado.auth import (
    +    OpenIdMixin,
    +    OAuthMixin,
    +    OAuth2Mixin,
    +    GoogleOAuth2Mixin,
    +    FacebookGraphMixin,
    +    TwitterMixin,
    +)
    +from tornado.escape import json_decode
    +from tornado import gen
    +from tornado.httpclient import HTTPClientError
    +from tornado.httputil import url_concat
    +from tornado.log import app_log
    +from tornado.testing import AsyncHTTPTestCase, ExpectLog
    +from tornado.web import RequestHandler, Application, HTTPError
    +
    +try:
    +    from unittest import mock
    +except ImportError:
    +    mock = None  # type: ignore
    +
    +
    +class OpenIdClientLoginHandler(RequestHandler, OpenIdMixin):
    +    def initialize(self, test):
    +        self._OPENID_ENDPOINT = test.get_url("/openid/server/authenticate")
    +
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("openid.mode", None):
    +            user = yield self.get_authenticated_user(
    +                http_client=self.settings["http_client"]
    +            )
    +            if user is None:
    +                raise Exception("user is None")
    +            self.finish(user)
    +            return
    +        res = self.authenticate_redirect()
    +        assert res is None
    +
    +
    +class OpenIdServerAuthenticateHandler(RequestHandler):
    +    def post(self):
    +        if self.get_argument("openid.mode") != "check_authentication":
    +            raise Exception("incorrect openid.mode %r")
    +        self.write("is_valid:true")
    +
    +
    +class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin):
    +    def initialize(self, test, version):
    +        self._OAUTH_VERSION = version
    +        self._OAUTH_REQUEST_TOKEN_URL = test.get_url("/oauth1/server/request_token")
    +        self._OAUTH_AUTHORIZE_URL = test.get_url("/oauth1/server/authorize")
    +        self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/oauth1/server/access_token")
    +
    +    def _oauth_consumer_token(self):
    +        return dict(key="asdf", secret="qwer")
    +
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("oauth_token", None):
    +            user = yield self.get_authenticated_user(
    +                http_client=self.settings["http_client"]
    +            )
    +            if user is None:
    +                raise Exception("user is None")
    +            self.finish(user)
    +            return
    +        yield self.authorize_redirect(http_client=self.settings["http_client"])
    +
    +    @gen.coroutine
    +    def _oauth_get_user_future(self, access_token):
    +        if self.get_argument("fail_in_get_user", None):
    +            raise Exception("failing in get_user")
    +        if access_token != dict(key="uiop", secret="5678"):
    +            raise Exception("incorrect access token %r" % access_token)
    +        return dict(email="foo@example.com")
    +
    +
    +class OAuth1ClientLoginCoroutineHandler(OAuth1ClientLoginHandler):
    +    """Replaces OAuth1ClientLoginCoroutineHandler's get() with a coroutine."""
    +
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("oauth_token", None):
    +            # Ensure that any exceptions are set on the returned Future,
    +            # not simply thrown into the surrounding StackContext.
    +            try:
    +                yield self.get_authenticated_user()
    +            except Exception as e:
    +                self.set_status(503)
    +                self.write("got exception: %s" % e)
    +        else:
    +            yield self.authorize_redirect()
    +
    +
    +class OAuth1ClientRequestParametersHandler(RequestHandler, OAuthMixin):
    +    def initialize(self, version):
    +        self._OAUTH_VERSION = version
    +
    +    def _oauth_consumer_token(self):
    +        return dict(key="asdf", secret="qwer")
    +
    +    def get(self):
    +        params = self._oauth_request_parameters(
    +            "http://www.example.com/api/asdf",
    +            dict(key="uiop", secret="5678"),
    +            parameters=dict(foo="bar"),
    +        )
    +        self.write(params)
    +
    +
    +class OAuth1ServerRequestTokenHandler(RequestHandler):
    +    def get(self):
    +        self.write("oauth_token=zxcv&oauth_token_secret=1234")
    +
    +
    +class OAuth1ServerAccessTokenHandler(RequestHandler):
    +    def get(self):
    +        self.write("oauth_token=uiop&oauth_token_secret=5678")
    +
    +
    +class OAuth2ClientLoginHandler(RequestHandler, OAuth2Mixin):
    +    def initialize(self, test):
    +        self._OAUTH_AUTHORIZE_URL = test.get_url("/oauth2/server/authorize")
    +
    +    def get(self):
    +        res = self.authorize_redirect()
    +        assert res is None
    +
    +
    +class FacebookClientLoginHandler(RequestHandler, FacebookGraphMixin):
    +    def initialize(self, test):
    +        self._OAUTH_AUTHORIZE_URL = test.get_url("/facebook/server/authorize")
    +        self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/facebook/server/access_token")
    +        self._FACEBOOK_BASE_URL = test.get_url("/facebook/server")
    +
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("code", None):
    +            user = yield self.get_authenticated_user(
    +                redirect_uri=self.request.full_url(),
    +                client_id=self.settings["facebook_api_key"],
    +                client_secret=self.settings["facebook_secret"],
    +                code=self.get_argument("code"),
    +            )
    +            self.write(user)
    +        else:
    +            yield self.authorize_redirect(
    +                redirect_uri=self.request.full_url(),
    +                client_id=self.settings["facebook_api_key"],
    +                extra_params={"scope": "read_stream,offline_access"},
    +            )
    +
    +
    +class FacebookServerAccessTokenHandler(RequestHandler):
    +    def get(self):
    +        self.write(dict(access_token="asdf", expires_in=3600))
    +
    +
    +class FacebookServerMeHandler(RequestHandler):
    +    def get(self):
    +        self.write("{}")
    +
    +
    +class TwitterClientHandler(RequestHandler, TwitterMixin):
    +    def initialize(self, test):
    +        self._OAUTH_REQUEST_TOKEN_URL = test.get_url("/oauth1/server/request_token")
    +        self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/twitter/server/access_token")
    +        self._OAUTH_AUTHORIZE_URL = test.get_url("/oauth1/server/authorize")
    +        self._OAUTH_AUTHENTICATE_URL = test.get_url("/twitter/server/authenticate")
    +        self._TWITTER_BASE_URL = test.get_url("/twitter/api")
    +
    +    def get_auth_http_client(self):
    +        return self.settings["http_client"]
    +
    +
    +class TwitterClientLoginHandler(TwitterClientHandler):
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("oauth_token", None):
    +            user = yield self.get_authenticated_user()
    +            if user is None:
    +                raise Exception("user is None")
    +            self.finish(user)
    +            return
    +        yield self.authorize_redirect()
    +
    +
    +class TwitterClientAuthenticateHandler(TwitterClientHandler):
    +    # Like TwitterClientLoginHandler, but uses authenticate_redirect
    +    # instead of authorize_redirect.
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("oauth_token", None):
    +            user = yield self.get_authenticated_user()
    +            if user is None:
    +                raise Exception("user is None")
    +            self.finish(user)
    +            return
    +        yield self.authenticate_redirect()
    +
    +
    +class TwitterClientLoginGenCoroutineHandler(TwitterClientHandler):
    +    @gen.coroutine
    +    def get(self):
    +        if self.get_argument("oauth_token", None):
    +            user = yield self.get_authenticated_user()
    +            self.finish(user)
    +        else:
    +            # New style: with @gen.coroutine the result must be yielded
    +            # or else the request will be auto-finished too soon.
    +            yield self.authorize_redirect()
    +
    +
    +class TwitterClientShowUserHandler(TwitterClientHandler):
    +    @gen.coroutine
    +    def get(self):
    +        # TODO: would be nice to go through the login flow instead of
    +        # cheating with a hard-coded access token.
    +        try:
    +            response = yield self.twitter_request(
    +                "/users/show/%s" % self.get_argument("name"),
    +                access_token=dict(key="hjkl", secret="vbnm"),
    +            )
    +        except HTTPClientError:
    +            # TODO(bdarnell): Should we catch HTTP errors and
    +            # transform some of them (like 403s) into AuthError?
    +            self.set_status(500)
    +            self.finish("error from twitter request")
    +        else:
    +            self.finish(response)
    +
    +
    +class TwitterServerAccessTokenHandler(RequestHandler):
    +    def get(self):
    +        self.write("oauth_token=hjkl&oauth_token_secret=vbnm&screen_name=foo")
    +
    +
    +class TwitterServerShowUserHandler(RequestHandler):
    +    def get(self, screen_name):
    +        if screen_name == "error":
    +            raise HTTPError(500)
    +        assert "oauth_nonce" in self.request.arguments
    +        assert "oauth_timestamp" in self.request.arguments
    +        assert "oauth_signature" in self.request.arguments
    +        assert self.get_argument("oauth_consumer_key") == "test_twitter_consumer_key"
    +        assert self.get_argument("oauth_signature_method") == "HMAC-SHA1"
    +        assert self.get_argument("oauth_version") == "1.0"
    +        assert self.get_argument("oauth_token") == "hjkl"
    +        self.write(dict(screen_name=screen_name, name=screen_name.capitalize()))
    +
    +
    +class TwitterServerVerifyCredentialsHandler(RequestHandler):
    +    def get(self):
    +        assert "oauth_nonce" in self.request.arguments
    +        assert "oauth_timestamp" in self.request.arguments
    +        assert "oauth_signature" in self.request.arguments
    +        assert self.get_argument("oauth_consumer_key") == "test_twitter_consumer_key"
    +        assert self.get_argument("oauth_signature_method") == "HMAC-SHA1"
    +        assert self.get_argument("oauth_version") == "1.0"
    +        assert self.get_argument("oauth_token") == "hjkl"
    +        self.write(dict(screen_name="foo", name="Foo"))
    +
    +
    +class AuthTest(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application(
    +            [
    +                # test endpoints
    +                ("/openid/client/login", OpenIdClientLoginHandler, dict(test=self)),
    +                (
    +                    "/oauth10/client/login",
    +                    OAuth1ClientLoginHandler,
    +                    dict(test=self, version="1.0"),
    +                ),
    +                (
    +                    "/oauth10/client/request_params",
    +                    OAuth1ClientRequestParametersHandler,
    +                    dict(version="1.0"),
    +                ),
    +                (
    +                    "/oauth10a/client/login",
    +                    OAuth1ClientLoginHandler,
    +                    dict(test=self, version="1.0a"),
    +                ),
    +                (
    +                    "/oauth10a/client/login_coroutine",
    +                    OAuth1ClientLoginCoroutineHandler,
    +                    dict(test=self, version="1.0a"),
    +                ),
    +                (
    +                    "/oauth10a/client/request_params",
    +                    OAuth1ClientRequestParametersHandler,
    +                    dict(version="1.0a"),
    +                ),
    +                ("/oauth2/client/login", OAuth2ClientLoginHandler, dict(test=self)),
    +                ("/facebook/client/login", FacebookClientLoginHandler, dict(test=self)),
    +                ("/twitter/client/login", TwitterClientLoginHandler, dict(test=self)),
    +                (
    +                    "/twitter/client/authenticate",
    +                    TwitterClientAuthenticateHandler,
    +                    dict(test=self),
    +                ),
    +                (
    +                    "/twitter/client/login_gen_coroutine",
    +                    TwitterClientLoginGenCoroutineHandler,
    +                    dict(test=self),
    +                ),
    +                (
    +                    "/twitter/client/show_user",
    +                    TwitterClientShowUserHandler,
    +                    dict(test=self),
    +                ),
    +                # simulated servers
    +                ("/openid/server/authenticate", OpenIdServerAuthenticateHandler),
    +                ("/oauth1/server/request_token", OAuth1ServerRequestTokenHandler),
    +                ("/oauth1/server/access_token", OAuth1ServerAccessTokenHandler),
    +                ("/facebook/server/access_token", FacebookServerAccessTokenHandler),
    +                ("/facebook/server/me", FacebookServerMeHandler),
    +                ("/twitter/server/access_token", TwitterServerAccessTokenHandler),
    +                (r"/twitter/api/users/show/(.*)\.json", TwitterServerShowUserHandler),
    +                (
    +                    r"/twitter/api/account/verify_credentials\.json",
    +                    TwitterServerVerifyCredentialsHandler,
    +                ),
    +            ],
    +            http_client=self.http_client,
    +            twitter_consumer_key="test_twitter_consumer_key",
    +            twitter_consumer_secret="test_twitter_consumer_secret",
    +            facebook_api_key="test_facebook_api_key",
    +            facebook_secret="test_facebook_secret",
    +        )
    +
    +    def test_openid_redirect(self):
    +        response = self.fetch("/openid/client/login", follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue("/openid/server/authenticate?" in response.headers["Location"])
    +
    +    def test_openid_get_user(self):
    +        response = self.fetch(
    +            "/openid/client/login?openid.mode=blah"
    +            "&openid.ns.ax=http://openid.net/srv/ax/1.0"
    +            "&openid.ax.type.email=http://axschema.org/contact/email"
    +            "&openid.ax.value.email=foo@example.com"
    +        )
    +        response.rethrow()
    +        parsed = json_decode(response.body)
    +        self.assertEqual(parsed["email"], "foo@example.com")
    +
    +    def test_oauth10_redirect(self):
    +        response = self.fetch("/oauth10/client/login", follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue(
    +            response.headers["Location"].endswith(
    +                "/oauth1/server/authorize?oauth_token=zxcv"
    +            )
    +        )
    +        # the cookie is base64('zxcv')|base64('1234')
    +        self.assertTrue(
    +            '_oauth_request_token="enhjdg==|MTIzNA=="'
    +            in response.headers["Set-Cookie"],
    +            response.headers["Set-Cookie"],
    +        )
    +
    +    def test_oauth10_get_user(self):
    +        response = self.fetch(
    +            "/oauth10/client/login?oauth_token=zxcv",
    +            headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
    +        )
    +        response.rethrow()
    +        parsed = json_decode(response.body)
    +        self.assertEqual(parsed["email"], "foo@example.com")
    +        self.assertEqual(parsed["access_token"], dict(key="uiop", secret="5678"))
    +
    +    def test_oauth10_request_parameters(self):
    +        response = self.fetch("/oauth10/client/request_params")
    +        response.rethrow()
    +        parsed = json_decode(response.body)
    +        self.assertEqual(parsed["oauth_consumer_key"], "asdf")
    +        self.assertEqual(parsed["oauth_token"], "uiop")
    +        self.assertTrue("oauth_nonce" in parsed)
    +        self.assertTrue("oauth_signature" in parsed)
    +
    +    def test_oauth10a_redirect(self):
    +        response = self.fetch("/oauth10a/client/login", follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue(
    +            response.headers["Location"].endswith(
    +                "/oauth1/server/authorize?oauth_token=zxcv"
    +            )
    +        )
    +        # the cookie is base64('zxcv')|base64('1234')
    +        self.assertTrue(
    +            '_oauth_request_token="enhjdg==|MTIzNA=="'
    +            in response.headers["Set-Cookie"],
    +            response.headers["Set-Cookie"],
    +        )
    +
    +    @unittest.skipIf(mock is None, "mock package not present")
    +    def test_oauth10a_redirect_error(self):
    +        with mock.patch.object(OAuth1ServerRequestTokenHandler, "get") as get:
    +            get.side_effect = Exception("boom")
    +            with ExpectLog(app_log, "Uncaught exception"):
    +                response = self.fetch("/oauth10a/client/login", follow_redirects=False)
    +            self.assertEqual(response.code, 500)
    +
    +    def test_oauth10a_get_user(self):
    +        response = self.fetch(
    +            "/oauth10a/client/login?oauth_token=zxcv",
    +            headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
    +        )
    +        response.rethrow()
    +        parsed = json_decode(response.body)
    +        self.assertEqual(parsed["email"], "foo@example.com")
    +        self.assertEqual(parsed["access_token"], dict(key="uiop", secret="5678"))
    +
    +    def test_oauth10a_request_parameters(self):
    +        response = self.fetch("/oauth10a/client/request_params")
    +        response.rethrow()
    +        parsed = json_decode(response.body)
    +        self.assertEqual(parsed["oauth_consumer_key"], "asdf")
    +        self.assertEqual(parsed["oauth_token"], "uiop")
    +        self.assertTrue("oauth_nonce" in parsed)
    +        self.assertTrue("oauth_signature" in parsed)
    +
    +    def test_oauth10a_get_user_coroutine_exception(self):
    +        response = self.fetch(
    +            "/oauth10a/client/login_coroutine?oauth_token=zxcv&fail_in_get_user=true",
    +            headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
    +        )
    +        self.assertEqual(response.code, 503)
    +
    +    def test_oauth2_redirect(self):
    +        response = self.fetch("/oauth2/client/login", follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue("/oauth2/server/authorize?" in response.headers["Location"])
    +
    +    def test_facebook_login(self):
    +        response = self.fetch("/facebook/client/login", follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue("/facebook/server/authorize?" in response.headers["Location"])
    +        response = self.fetch(
    +            "/facebook/client/login?code=1234", follow_redirects=False
    +        )
    +        self.assertEqual(response.code, 200)
    +        user = json_decode(response.body)
    +        self.assertEqual(user["access_token"], "asdf")
    +        self.assertEqual(user["session_expires"], "3600")
    +
    +    def base_twitter_redirect(self, url):
    +        # Same as test_oauth10a_redirect
    +        response = self.fetch(url, follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue(
    +            response.headers["Location"].endswith(
    +                "/oauth1/server/authorize?oauth_token=zxcv"
    +            )
    +        )
    +        # the cookie is base64('zxcv')|base64('1234')
    +        self.assertTrue(
    +            '_oauth_request_token="enhjdg==|MTIzNA=="'
    +            in response.headers["Set-Cookie"],
    +            response.headers["Set-Cookie"],
    +        )
    +
    +    def test_twitter_redirect(self):
    +        self.base_twitter_redirect("/twitter/client/login")
    +
    +    def test_twitter_redirect_gen_coroutine(self):
    +        self.base_twitter_redirect("/twitter/client/login_gen_coroutine")
    +
    +    def test_twitter_authenticate_redirect(self):
    +        response = self.fetch("/twitter/client/authenticate", follow_redirects=False)
    +        self.assertEqual(response.code, 302)
    +        self.assertTrue(
    +            response.headers["Location"].endswith(
    +                "/twitter/server/authenticate?oauth_token=zxcv"
    +            ),
    +            response.headers["Location"],
    +        )
    +        # the cookie is base64('zxcv')|base64('1234')
    +        self.assertTrue(
    +            '_oauth_request_token="enhjdg==|MTIzNA=="'
    +            in response.headers["Set-Cookie"],
    +            response.headers["Set-Cookie"],
    +        )
    +
    +    def test_twitter_get_user(self):
    +        response = self.fetch(
    +            "/twitter/client/login?oauth_token=zxcv",
    +            headers={"Cookie": "_oauth_request_token=enhjdg==|MTIzNA=="},
    +        )
    +        response.rethrow()
    +        parsed = json_decode(response.body)
    +        self.assertEqual(
    +            parsed,
    +            {
    +                u"access_token": {
    +                    u"key": u"hjkl",
    +                    u"screen_name": u"foo",
    +                    u"secret": u"vbnm",
    +                },
    +                u"name": u"Foo",
    +                u"screen_name": u"foo",
    +                u"username": u"foo",
    +            },
    +        )
    +
    +    def test_twitter_show_user(self):
    +        response = self.fetch("/twitter/client/show_user?name=somebody")
    +        response.rethrow()
    +        self.assertEqual(
    +            json_decode(response.body), {"name": "Somebody", "screen_name": "somebody"}
    +        )
    +
    +    def test_twitter_show_user_error(self):
    +        response = self.fetch("/twitter/client/show_user?name=error")
    +        self.assertEqual(response.code, 500)
    +        self.assertEqual(response.body, b"error from twitter request")
    +
    +
    +class GoogleLoginHandler(RequestHandler, GoogleOAuth2Mixin):
    +    def initialize(self, test):
    +        self.test = test
    +        self._OAUTH_REDIRECT_URI = test.get_url("/client/login")
    +        self._OAUTH_AUTHORIZE_URL = test.get_url("/google/oauth2/authorize")
    +        self._OAUTH_ACCESS_TOKEN_URL = test.get_url("/google/oauth2/token")
    +
    +    @gen.coroutine
    +    def get(self):
    +        code = self.get_argument("code", None)
    +        if code is not None:
    +            # retrieve authenticate google user
    +            access = yield self.get_authenticated_user(self._OAUTH_REDIRECT_URI, code)
    +            user = yield self.oauth2_request(
    +                self.test.get_url("/google/oauth2/userinfo"),
    +                access_token=access["access_token"],
    +            )
    +            # return the user and access token as json
    +            user["access_token"] = access["access_token"]
    +            self.write(user)
    +        else:
    +            yield self.authorize_redirect(
    +                redirect_uri=self._OAUTH_REDIRECT_URI,
    +                client_id=self.settings["google_oauth"]["key"],
    +                client_secret=self.settings["google_oauth"]["secret"],
    +                scope=["profile", "email"],
    +                response_type="code",
    +                extra_params={"prompt": "select_account"},
    +            )
    +
    +
    +class GoogleOAuth2AuthorizeHandler(RequestHandler):
    +    def get(self):
    +        # issue a fake auth code and redirect to redirect_uri
    +        code = "fake-authorization-code"
    +        self.redirect(url_concat(self.get_argument("redirect_uri"), dict(code=code)))
    +
    +
    +class GoogleOAuth2TokenHandler(RequestHandler):
    +    def post(self):
    +        assert self.get_argument("code") == "fake-authorization-code"
    +        # issue a fake token
    +        self.finish(
    +            {"access_token": "fake-access-token", "expires_in": "never-expires"}
    +        )
    +
    +
    +class GoogleOAuth2UserinfoHandler(RequestHandler):
    +    def get(self):
    +        assert self.get_argument("access_token") == "fake-access-token"
    +        # return a fake user
    +        self.finish({"name": "Foo", "email": "foo@example.com"})
    +
    +
    +class GoogleOAuth2Test(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application(
    +            [
    +                # test endpoints
    +                ("/client/login", GoogleLoginHandler, dict(test=self)),
    +                # simulated google authorization server endpoints
    +                ("/google/oauth2/authorize", GoogleOAuth2AuthorizeHandler),
    +                ("/google/oauth2/token", GoogleOAuth2TokenHandler),
    +                ("/google/oauth2/userinfo", GoogleOAuth2UserinfoHandler),
    +            ],
    +            google_oauth={
    +                "key": "fake_google_client_id",
    +                "secret": "fake_google_client_secret",
    +            },
    +        )
    +
    +    def test_google_login(self):
    +        response = self.fetch("/client/login")
    +        self.assertDictEqual(
    +            {
    +                u"name": u"Foo",
    +                u"email": u"foo@example.com",
    +                u"access_token": u"fake-access-token",
    +            },
    +            json_decode(response.body),
    +        )
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/autoreload_test.py b/venv/lib/python3.7/site-packages/tornado/test/autoreload_test.py
    new file mode 100644
    index 0000000..be481e1
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/autoreload_test.py
    @@ -0,0 +1,127 @@
    +import os
    +import shutil
    +import subprocess
    +from subprocess import Popen
    +import sys
    +from tempfile import mkdtemp
    +import time
    +import unittest
    +
    +
    +class AutoreloadTest(unittest.TestCase):
    +    def setUp(self):
    +        self.path = mkdtemp()
    +
    +    def tearDown(self):
    +        try:
    +            shutil.rmtree(self.path)
    +        except OSError:
    +            # Windows disallows deleting files that are in use by
    +            # another process, and even though we've waited for our
    +            # child process below, it appears that its lock on these
    +            # files is not guaranteed to be released by this point.
    +            # Sleep and try again (once).
    +            time.sleep(1)
    +            shutil.rmtree(self.path)
    +
    +    def test_reload_module(self):
    +        main = """\
    +import os
    +import sys
    +
    +from tornado import autoreload
    +
    +# This import will fail if path is not set up correctly
    +import testapp
    +
    +print('Starting')
    +if 'TESTAPP_STARTED' not in os.environ:
    +    os.environ['TESTAPP_STARTED'] = '1'
    +    sys.stdout.flush()
    +    autoreload._reload()
    +"""
    +
    +        # Create temporary test application
    +        os.mkdir(os.path.join(self.path, "testapp"))
    +        open(os.path.join(self.path, "testapp/__init__.py"), "w").close()
    +        with open(os.path.join(self.path, "testapp/__main__.py"), "w") as f:
    +            f.write(main)
    +
    +        # Make sure the tornado module under test is available to the test
    +        # application
    +        pythonpath = os.getcwd()
    +        if "PYTHONPATH" in os.environ:
    +            pythonpath += os.pathsep + os.environ["PYTHONPATH"]
    +
    +        p = Popen(
    +            [sys.executable, "-m", "testapp"],
    +            stdout=subprocess.PIPE,
    +            cwd=self.path,
    +            env=dict(os.environ, PYTHONPATH=pythonpath),
    +            universal_newlines=True,
    +        )
    +        out = p.communicate()[0]
    +        self.assertEqual(out, "Starting\nStarting\n")
    +
    +    def test_reload_wrapper_preservation(self):
    +        # This test verifies that when `python -m tornado.autoreload`
    +        # is used on an application that also has an internal
    +        # autoreload, the reload wrapper is preserved on restart.
    +        main = """\
    +import os
    +import sys
    +
    +# This import will fail if path is not set up correctly
    +import testapp
    +
    +if 'tornado.autoreload' not in sys.modules:
    +    raise Exception('started without autoreload wrapper')
    +
    +import tornado.autoreload
    +
    +print('Starting')
    +sys.stdout.flush()
    +if 'TESTAPP_STARTED' not in os.environ:
    +    os.environ['TESTAPP_STARTED'] = '1'
    +    # Simulate an internal autoreload (one not caused
    +    # by the wrapper).
    +    tornado.autoreload._reload()
    +else:
    +    # Exit directly so autoreload doesn't catch it.
    +    os._exit(0)
    +"""
    +
    +        # Create temporary test application
    +        os.mkdir(os.path.join(self.path, "testapp"))
    +        init_file = os.path.join(self.path, "testapp", "__init__.py")
    +        open(init_file, "w").close()
    +        main_file = os.path.join(self.path, "testapp", "__main__.py")
    +        with open(main_file, "w") as f:
    +            f.write(main)
    +
    +        # Make sure the tornado module under test is available to the test
    +        # application
    +        pythonpath = os.getcwd()
    +        if "PYTHONPATH" in os.environ:
    +            pythonpath += os.pathsep + os.environ["PYTHONPATH"]
    +
    +        autoreload_proc = Popen(
    +            [sys.executable, "-m", "tornado.autoreload", "-m", "testapp"],
    +            stdout=subprocess.PIPE,
    +            cwd=self.path,
    +            env=dict(os.environ, PYTHONPATH=pythonpath),
    +            universal_newlines=True,
    +        )
    +
    +        # This timeout needs to be fairly generous for pypy due to jit
    +        # warmup costs.
    +        for i in range(40):
    +            if autoreload_proc.poll() is not None:
    +                break
    +            time.sleep(0.1)
    +        else:
    +            autoreload_proc.kill()
    +            raise Exception("subprocess failed to terminate")
    +
    +        out = autoreload_proc.communicate()[0]
    +        self.assertEqual(out, "Starting\n" * 2)
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/concurrent_test.py b/venv/lib/python3.7/site-packages/tornado/test/concurrent_test.py
    new file mode 100644
    index 0000000..488b7dd
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/concurrent_test.py
    @@ -0,0 +1,209 @@
    +#
    +# 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.
    +from concurrent import futures
    +import logging
    +import re
    +import socket
    +import unittest
    +
    +from tornado.concurrent import (
    +    Future,
    +    run_on_executor,
    +    future_set_result_unless_cancelled,
    +)
    +from tornado.escape import utf8, to_unicode
    +from tornado import gen
    +from tornado.iostream import IOStream
    +from tornado.tcpserver import TCPServer
    +from tornado.testing import AsyncTestCase, bind_unused_port, gen_test
    +
    +
    +class MiscFutureTest(AsyncTestCase):
    +    def test_future_set_result_unless_cancelled(self):
    +        fut = Future()  # type: Future[int]
    +        future_set_result_unless_cancelled(fut, 42)
    +        self.assertEqual(fut.result(), 42)
    +        self.assertFalse(fut.cancelled())
    +
    +        fut = Future()
    +        fut.cancel()
    +        is_cancelled = fut.cancelled()
    +        future_set_result_unless_cancelled(fut, 42)
    +        self.assertEqual(fut.cancelled(), is_cancelled)
    +        if not is_cancelled:
    +            self.assertEqual(fut.result(), 42)
    +
    +
    +# The following series of classes demonstrate and test various styles
    +# of use, with and without generators and futures.
    +
    +
    +class CapServer(TCPServer):
    +    @gen.coroutine
    +    def handle_stream(self, stream, address):
    +        data = yield stream.read_until(b"\n")
    +        data = to_unicode(data)
    +        if data == data.upper():
    +            stream.write(b"error\talready capitalized\n")
    +        else:
    +            # data already has \n
    +            stream.write(utf8("ok\t%s" % data.upper()))
    +        stream.close()
    +
    +
    +class CapError(Exception):
    +    pass
    +
    +
    +class BaseCapClient(object):
    +    def __init__(self, port):
    +        self.port = port
    +
    +    def process_response(self, data):
    +        m = re.match("(.*)\t(.*)\n", to_unicode(data))
    +        if m is None:
    +            raise Exception("did not match")
    +        status, message = m.groups()
    +        if status == "ok":
    +            return message
    +        else:
    +            raise CapError(message)
    +
    +
    +class GeneratorCapClient(BaseCapClient):
    +    @gen.coroutine
    +    def capitalize(self, request_data):
    +        logging.debug("capitalize")
    +        stream = IOStream(socket.socket())
    +        logging.debug("connecting")
    +        yield stream.connect(("127.0.0.1", self.port))
    +        stream.write(utf8(request_data + "\n"))
    +        logging.debug("reading")
    +        data = yield stream.read_until(b"\n")
    +        logging.debug("returning")
    +        stream.close()
    +        raise gen.Return(self.process_response(data))
    +
    +
    +class ClientTestMixin(object):
    +    def setUp(self):
    +        super(ClientTestMixin, self).setUp()  # type: ignore
    +        self.server = CapServer()
    +        sock, port = bind_unused_port()
    +        self.server.add_sockets([sock])
    +        self.client = self.client_class(port=port)
    +
    +    def tearDown(self):
    +        self.server.stop()
    +        super(ClientTestMixin, self).tearDown()  # type: ignore
    +
    +    def test_future(self):
    +        future = self.client.capitalize("hello")
    +        self.io_loop.add_future(future, self.stop)
    +        self.wait()
    +        self.assertEqual(future.result(), "HELLO")
    +
    +    def test_future_error(self):
    +        future = self.client.capitalize("HELLO")
    +        self.io_loop.add_future(future, self.stop)
    +        self.wait()
    +        self.assertRaisesRegexp(CapError, "already capitalized", future.result)
    +
    +    def test_generator(self):
    +        @gen.coroutine
    +        def f():
    +            result = yield self.client.capitalize("hello")
    +            self.assertEqual(result, "HELLO")
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_generator_error(self):
    +        @gen.coroutine
    +        def f():
    +            with self.assertRaisesRegexp(CapError, "already capitalized"):
    +                yield self.client.capitalize("HELLO")
    +
    +        self.io_loop.run_sync(f)
    +
    +
    +class GeneratorClientTest(ClientTestMixin, AsyncTestCase):
    +    client_class = GeneratorCapClient
    +
    +
    +class RunOnExecutorTest(AsyncTestCase):
    +    @gen_test
    +    def test_no_calling(self):
    +        class Object(object):
    +            def __init__(self):
    +                self.executor = futures.thread.ThreadPoolExecutor(1)
    +
    +            @run_on_executor
    +            def f(self):
    +                return 42
    +
    +        o = Object()
    +        answer = yield o.f()
    +        self.assertEqual(answer, 42)
    +
    +    @gen_test
    +    def test_call_with_no_args(self):
    +        class Object(object):
    +            def __init__(self):
    +                self.executor = futures.thread.ThreadPoolExecutor(1)
    +
    +            @run_on_executor()
    +            def f(self):
    +                return 42
    +
    +        o = Object()
    +        answer = yield o.f()
    +        self.assertEqual(answer, 42)
    +
    +    @gen_test
    +    def test_call_with_executor(self):
    +        class Object(object):
    +            def __init__(self):
    +                self.__executor = futures.thread.ThreadPoolExecutor(1)
    +
    +            @run_on_executor(executor="_Object__executor")
    +            def f(self):
    +                return 42
    +
    +        o = Object()
    +        answer = yield o.f()
    +        self.assertEqual(answer, 42)
    +
    +    @gen_test
    +    def test_async_await(self):
    +        class Object(object):
    +            def __init__(self):
    +                self.executor = futures.thread.ThreadPoolExecutor(1)
    +
    +            @run_on_executor()
    +            def f(self):
    +                return 42
    +
    +        o = Object()
    +
    +        async def f():
    +            answer = await o.f()
    +            return answer
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +
    +
    +if __name__ == "__main__":
    +    unittest.main()
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/csv_translations/fr_FR.csv b/venv/lib/python3.7/site-packages/tornado/test/csv_translations/fr_FR.csv
    new file mode 100644
    index 0000000..6321b6e
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/csv_translations/fr_FR.csv
    @@ -0,0 +1 @@
    +"school","école"
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/curl_httpclient_test.py b/venv/lib/python3.7/site-packages/tornado/test/curl_httpclient_test.py
    new file mode 100644
    index 0000000..ccb19fe
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/curl_httpclient_test.py
    @@ -0,0 +1,130 @@
    +# coding: utf-8
    +from hashlib import md5
    +import unittest
    +
    +from tornado.escape import utf8
    +from tornado.testing import AsyncHTTPTestCase
    +from tornado.test import httpclient_test
    +from tornado.web import Application, RequestHandler
    +
    +
    +try:
    +    import pycurl  # type: ignore
    +except ImportError:
    +    pycurl = None
    +
    +if pycurl is not None:
    +    from tornado.curl_httpclient import CurlAsyncHTTPClient
    +
    +
    +@unittest.skipIf(pycurl is None, "pycurl module not present")
    +class CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):
    +    def get_http_client(self):
    +        client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))
    +        # make sure AsyncHTTPClient magic doesn't give us the wrong class
    +        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))
    +        return client
    +
    +
    +class DigestAuthHandler(RequestHandler):
    +    def initialize(self, username, password):
    +        self.username = username
    +        self.password = password
    +
    +    def get(self):
    +        realm = "test"
    +        opaque = "asdf"
    +        # Real implementations would use a random nonce.
    +        nonce = "1234"
    +
    +        auth_header = self.request.headers.get("Authorization", None)
    +        if auth_header is not None:
    +            auth_mode, params = auth_header.split(" ", 1)
    +            assert auth_mode == "Digest"
    +            param_dict = {}
    +            for pair in params.split(","):
    +                k, v = pair.strip().split("=", 1)
    +                if v[0] == '"' and v[-1] == '"':
    +                    v = v[1:-1]
    +                param_dict[k] = v
    +            assert param_dict["realm"] == realm
    +            assert param_dict["opaque"] == opaque
    +            assert param_dict["nonce"] == nonce
    +            assert param_dict["username"] == self.username
    +            assert param_dict["uri"] == self.request.path
    +            h1 = md5(
    +                utf8("%s:%s:%s" % (self.username, realm, self.password))
    +            ).hexdigest()
    +            h2 = md5(
    +                utf8("%s:%s" % (self.request.method, self.request.path))
    +            ).hexdigest()
    +            digest = md5(utf8("%s:%s:%s" % (h1, nonce, h2))).hexdigest()
    +            if digest == param_dict["response"]:
    +                self.write("ok")
    +            else:
    +                self.write("fail")
    +        else:
    +            self.set_status(401)
    +            self.set_header(
    +                "WWW-Authenticate",
    +                'Digest realm="%s", nonce="%s", opaque="%s"' % (realm, nonce, opaque),
    +            )
    +
    +
    +class CustomReasonHandler(RequestHandler):
    +    def get(self):
    +        self.set_status(200, "Custom reason")
    +
    +
    +class CustomFailReasonHandler(RequestHandler):
    +    def get(self):
    +        self.set_status(400, "Custom reason")
    +
    +
    +@unittest.skipIf(pycurl is None, "pycurl module not present")
    +class CurlHTTPClientTestCase(AsyncHTTPTestCase):
    +    def setUp(self):
    +        super(CurlHTTPClientTestCase, self).setUp()
    +        self.http_client = self.create_client()
    +
    +    def get_app(self):
    +        return Application(
    +            [
    +                ("/digest", DigestAuthHandler, {"username": "foo", "password": "bar"}),
    +                (
    +                    "/digest_non_ascii",
    +                    DigestAuthHandler,
    +                    {"username": "foo", "password": "barユ£"},
    +                ),
    +                ("/custom_reason", CustomReasonHandler),
    +                ("/custom_fail_reason", CustomFailReasonHandler),
    +            ]
    +        )
    +
    +    def create_client(self, **kwargs):
    +        return CurlAsyncHTTPClient(
    +            force_instance=True, defaults=dict(allow_ipv6=False), **kwargs
    +        )
    +
    +    def test_digest_auth(self):
    +        response = self.fetch(
    +            "/digest", auth_mode="digest", auth_username="foo", auth_password="bar"
    +        )
    +        self.assertEqual(response.body, b"ok")
    +
    +    def test_custom_reason(self):
    +        response = self.fetch("/custom_reason")
    +        self.assertEqual(response.reason, "Custom reason")
    +
    +    def test_fail_custom_reason(self):
    +        response = self.fetch("/custom_fail_reason")
    +        self.assertEqual(str(response.error), "HTTP 400: Custom reason")
    +
    +    def test_digest_auth_non_ascii(self):
    +        response = self.fetch(
    +            "/digest_non_ascii",
    +            auth_mode="digest",
    +            auth_username="foo",
    +            auth_password="barユ£",
    +        )
    +        self.assertEqual(response.body, b"ok")
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/escape_test.py b/venv/lib/python3.7/site-packages/tornado/test/escape_test.py
    new file mode 100644
    index 0000000..d8f95e4
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/escape_test.py
    @@ -0,0 +1,322 @@
    +import unittest
    +
    +import tornado.escape
    +from tornado.escape import (
    +    utf8,
    +    xhtml_escape,
    +    xhtml_unescape,
    +    url_escape,
    +    url_unescape,
    +    to_unicode,
    +    json_decode,
    +    json_encode,
    +    squeeze,
    +    recursive_unicode,
    +)
    +from tornado.util import unicode_type
    +
    +from typing import List, Tuple, Union, Dict, Any  # noqa: F401
    +
    +linkify_tests = [
    +    # (input, linkify_kwargs, expected_output)
    +    (
    +        "hello http://world.com/!",
    +        {},
    +        u'hello http://world.com/!',
    +    ),
    +    (
    +        "hello http://world.com/with?param=true&stuff=yes",
    +        {},
    +        u'hello http://world.com/with?param=true&stuff=yes',  # noqa: E501
    +    ),
    +    # an opened paren followed by many chars killed Gruber's regex
    +    (
    +        "http://url.com/w(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    +        {},
    +        u'http://url.com/w(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',  # noqa: E501
    +    ),
    +    # as did too many dots at the end
    +    (
    +        "http://url.com/withmany.......................................",
    +        {},
    +        u'http://url.com/withmany.......................................',  # noqa: E501
    +    ),
    +    (
    +        "http://url.com/withmany((((((((((((((((((((((((((((((((((a)",
    +        {},
    +        u'http://url.com/withmany((((((((((((((((((((((((((((((((((a)',  # noqa: E501
    +    ),
    +    # some examples from http://daringfireball.net/2009/11/liberal_regex_for_matching_urls
    +    # plus a fex extras (such as multiple parentheses).
    +    (
    +        "http://foo.com/blah_blah",
    +        {},
    +        u'http://foo.com/blah_blah',
    +    ),
    +    (
    +        "http://foo.com/blah_blah/",
    +        {},
    +        u'http://foo.com/blah_blah/',
    +    ),
    +    (
    +        "(Something like http://foo.com/blah_blah)",
    +        {},
    +        u'(Something like http://foo.com/blah_blah)',
    +    ),
    +    (
    +        "http://foo.com/blah_blah_(wikipedia)",
    +        {},
    +        u'http://foo.com/blah_blah_(wikipedia)',
    +    ),
    +    (
    +        "http://foo.com/blah_(blah)_(wikipedia)_blah",
    +        {},
    +        u'http://foo.com/blah_(blah)_(wikipedia)_blah',  # noqa: E501
    +    ),
    +    (
    +        "(Something like http://foo.com/blah_blah_(wikipedia))",
    +        {},
    +        u'(Something like http://foo.com/blah_blah_(wikipedia))',  # noqa: E501
    +    ),
    +    (
    +        "http://foo.com/blah_blah.",
    +        {},
    +        u'http://foo.com/blah_blah.',
    +    ),
    +    (
    +        "http://foo.com/blah_blah/.",
    +        {},
    +        u'http://foo.com/blah_blah/.',
    +    ),
    +    (
    +        "",
    +        {},
    +        u'<http://foo.com/blah_blah>',
    +    ),
    +    (
    +        "",
    +        {},
    +        u'<http://foo.com/blah_blah/>',
    +    ),
    +    (
    +        "http://foo.com/blah_blah,",
    +        {},
    +        u'http://foo.com/blah_blah,',
    +    ),
    +    (
    +        "http://www.example.com/wpstyle/?p=364.",
    +        {},
    +        u'http://www.example.com/wpstyle/?p=364.',  # noqa: E501
    +    ),
    +    (
    +        "rdar://1234",
    +        {"permitted_protocols": ["http", "rdar"]},
    +        u'rdar://1234',
    +    ),
    +    (
    +        "rdar:/1234",
    +        {"permitted_protocols": ["rdar"]},
    +        u'rdar:/1234',
    +    ),
    +    (
    +        "http://userid:password@example.com:8080",
    +        {},
    +        u'http://userid:password@example.com:8080',  # noqa: E501
    +    ),
    +    (
    +        "http://userid@example.com",
    +        {},
    +        u'http://userid@example.com',
    +    ),
    +    (
    +        "http://userid@example.com:8080",
    +        {},
    +        u'http://userid@example.com:8080',
    +    ),
    +    (
    +        "http://userid:password@example.com",
    +        {},
    +        u'http://userid:password@example.com',
    +    ),
    +    (
    +        "message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e",
    +        {"permitted_protocols": ["http", "message"]},
    +        u''
    +        u"message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e",
    +    ),
    +    (
    +        u"http://\u27a1.ws/\u4a39",
    +        {},
    +        u'http://\u27a1.ws/\u4a39',
    +    ),
    +    (
    +        "http://example.com",
    +        {},
    +        u'<tag>http://example.com</tag>',
    +    ),
    +    (
    +        "Just a www.example.com link.",
    +        {},
    +        u'Just a www.example.com link.',
    +    ),
    +    (
    +        "Just a www.example.com link.",
    +        {"require_protocol": True},
    +        u"Just a www.example.com link.",
    +    ),
    +    (
    +        "A http://reallylong.com/link/that/exceedsthelenglimit.html",
    +        {"require_protocol": True, "shorten": True},
    +        u'A http://reallylong.com/link...',  # noqa: E501
    +    ),
    +    (
    +        "A http://reallylongdomainnamethatwillbetoolong.com/hi!",
    +        {"shorten": True},
    +        u'A http://reallylongdomainnametha...!',  # noqa: E501
    +    ),
    +    (
    +        "A file:///passwords.txt and http://web.com link",
    +        {},
    +        u'A file:///passwords.txt and http://web.com link',
    +    ),
    +    (
    +        "A file:///passwords.txt and http://web.com link",
    +        {"permitted_protocols": ["file"]},
    +        u'A file:///passwords.txt and http://web.com link',
    +    ),
    +    (
    +        "www.external-link.com",
    +        {"extra_params": 'rel="nofollow" class="external"'},
    +        u'www.external-link.com',  # noqa: E501
    +    ),
    +    (
    +        "www.external-link.com and www.internal-link.com/blogs extra",
    +        {
    +            "extra_params": lambda href: 'class="internal"'
    +            if href.startswith("http://www.internal-link.com")
    +            else 'rel="nofollow" class="external"'
    +        },
    +        u'www.external-link.com'  # noqa: E501
    +        u' and www.internal-link.com/blogs extra',  # noqa: E501
    +    ),
    +    (
    +        "www.external-link.com",
    +        {"extra_params": lambda href: '    rel="nofollow" class="external"  '},
    +        u'www.external-link.com',  # noqa: E501
    +    ),
    +]  # type: List[Tuple[Union[str, bytes], Dict[str, Any], str]]
    +
    +
    +class EscapeTestCase(unittest.TestCase):
    +    def test_linkify(self):
    +        for text, kwargs, html in linkify_tests:
    +            linked = tornado.escape.linkify(text, **kwargs)
    +            self.assertEqual(linked, html)
    +
    +    def test_xhtml_escape(self):
    +        tests = [
    +            ("", "<foo>"),
    +            (u"", u"<foo>"),
    +            (b"", b"<foo>"),
    +            ("<>&\"'", "<>&"'"),
    +            ("&", "&amp;"),
    +            (u"<\u00e9>", u"<\u00e9>"),
    +            (b"<\xc3\xa9>", b"<\xc3\xa9>"),
    +        ]  # type: List[Tuple[Union[str, bytes], Union[str, bytes]]]
    +        for unescaped, escaped in tests:
    +            self.assertEqual(utf8(xhtml_escape(unescaped)), utf8(escaped))
    +            self.assertEqual(utf8(unescaped), utf8(xhtml_unescape(escaped)))
    +
    +    def test_xhtml_unescape_numeric(self):
    +        tests = [
    +            ("foo bar", "foo bar"),
    +            ("foo bar", "foo bar"),
    +            ("foo bar", "foo bar"),
    +            ("foo઼bar", u"foo\u0abcbar"),
    +            ("foo&#xyz;bar", "foo&#xyz;bar"),  # invalid encoding
    +            ("foo&#;bar", "foo&#;bar"),  # invalid encoding
    +            ("foo&#x;bar", "foo&#x;bar"),  # invalid encoding
    +        ]
    +        for escaped, unescaped in tests:
    +            self.assertEqual(unescaped, xhtml_unescape(escaped))
    +
    +    def test_url_escape_unicode(self):
    +        tests = [
    +            # byte strings are passed through as-is
    +            (u"\u00e9".encode("utf8"), "%C3%A9"),
    +            (u"\u00e9".encode("latin1"), "%E9"),
    +            # unicode strings become utf8
    +            (u"\u00e9", "%C3%A9"),
    +        ]  # type: List[Tuple[Union[str, bytes], str]]
    +        for unescaped, escaped in tests:
    +            self.assertEqual(url_escape(unescaped), escaped)
    +
    +    def test_url_unescape_unicode(self):
    +        tests = [
    +            ("%C3%A9", u"\u00e9", "utf8"),
    +            ("%C3%A9", u"\u00c3\u00a9", "latin1"),
    +            ("%C3%A9", utf8(u"\u00e9"), None),
    +        ]
    +        for escaped, unescaped, encoding in tests:
    +            # input strings to url_unescape should only contain ascii
    +            # characters, but make sure the function accepts both byte
    +            # and unicode strings.
    +            self.assertEqual(url_unescape(to_unicode(escaped), encoding), unescaped)
    +            self.assertEqual(url_unescape(utf8(escaped), encoding), unescaped)
    +
    +    def test_url_escape_quote_plus(self):
    +        unescaped = "+ #%"
    +        plus_escaped = "%2B+%23%25"
    +        escaped = "%2B%20%23%25"
    +        self.assertEqual(url_escape(unescaped), plus_escaped)
    +        self.assertEqual(url_escape(unescaped, plus=False), escaped)
    +        self.assertEqual(url_unescape(plus_escaped), unescaped)
    +        self.assertEqual(url_unescape(escaped, plus=False), unescaped)
    +        self.assertEqual(url_unescape(plus_escaped, encoding=None), utf8(unescaped))
    +        self.assertEqual(
    +            url_unescape(escaped, encoding=None, plus=False), utf8(unescaped)
    +        )
    +
    +    def test_escape_return_types(self):
    +        # On python2 the escape methods should generally return the same
    +        # type as their argument
    +        self.assertEqual(type(xhtml_escape("foo")), str)
    +        self.assertEqual(type(xhtml_escape(u"foo")), unicode_type)
    +
    +    def test_json_decode(self):
    +        # json_decode accepts both bytes and unicode, but strings it returns
    +        # are always unicode.
    +        self.assertEqual(json_decode(b'"foo"'), u"foo")
    +        self.assertEqual(json_decode(u'"foo"'), u"foo")
    +
    +        # Non-ascii bytes are interpreted as utf8
    +        self.assertEqual(json_decode(utf8(u'"\u00e9"')), u"\u00e9")
    +
    +    def test_json_encode(self):
    +        # json deals with strings, not bytes.  On python 2 byte strings will
    +        # convert automatically if they are utf8; on python 3 byte strings
    +        # are not allowed.
    +        self.assertEqual(json_decode(json_encode(u"\u00e9")), u"\u00e9")
    +        if bytes is str:
    +            self.assertEqual(json_decode(json_encode(utf8(u"\u00e9"))), u"\u00e9")
    +            self.assertRaises(UnicodeDecodeError, json_encode, b"\xe9")
    +
    +    def test_squeeze(self):
    +        self.assertEqual(
    +            squeeze(u"sequences     of    whitespace   chars"),
    +            u"sequences of whitespace chars",
    +        )
    +
    +    def test_recursive_unicode(self):
    +        tests = {
    +            "dict": {b"foo": b"bar"},
    +            "list": [b"foo", b"bar"],
    +            "tuple": (b"foo", b"bar"),
    +            "bytes": b"foo",
    +        }
    +        self.assertEqual(recursive_unicode(tests["dict"]), {u"foo": u"bar"})
    +        self.assertEqual(recursive_unicode(tests["list"]), [u"foo", u"bar"])
    +        self.assertEqual(recursive_unicode(tests["tuple"]), (u"foo", u"bar"))
    +        self.assertEqual(recursive_unicode(tests["bytes"]), u"foo")
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/gen_test.py b/venv/lib/python3.7/site-packages/tornado/test/gen_test.py
    new file mode 100644
    index 0000000..659e226
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/gen_test.py
    @@ -0,0 +1,1111 @@
    +import asyncio
    +from concurrent import futures
    +import gc
    +import datetime
    +import platform
    +import sys
    +import time
    +import weakref
    +import unittest
    +
    +from tornado.concurrent import Future
    +from tornado.log import app_log
    +from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test
    +from tornado.test.util import skipOnTravis, skipNotCPython
    +from tornado.web import Application, RequestHandler, HTTPError
    +
    +from tornado import gen
    +
    +try:
    +    import contextvars
    +except ImportError:
    +    contextvars = None  # type: ignore
    +
    +import typing
    +
    +if typing.TYPE_CHECKING:
    +    from typing import List, Optional  # noqa: F401
    +
    +
    +class GenBasicTest(AsyncTestCase):
    +    @gen.coroutine
    +    def delay(self, iterations, arg):
    +        """Returns arg after a number of IOLoop iterations."""
    +        for i in range(iterations):
    +            yield gen.moment
    +        raise gen.Return(arg)
    +
    +    @gen.coroutine
    +    def async_future(self, result):
    +        yield gen.moment
    +        return result
    +
    +    @gen.coroutine
    +    def async_exception(self, e):
    +        yield gen.moment
    +        raise e
    +
    +    @gen.coroutine
    +    def add_one_async(self, x):
    +        yield gen.moment
    +        raise gen.Return(x + 1)
    +
    +    def test_no_yield(self):
    +        @gen.coroutine
    +        def f():
    +            pass
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_exception_phase1(self):
    +        @gen.coroutine
    +        def f():
    +            1 / 0
    +
    +        self.assertRaises(ZeroDivisionError, self.io_loop.run_sync, f)
    +
    +    def test_exception_phase2(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            1 / 0
    +
    +        self.assertRaises(ZeroDivisionError, self.io_loop.run_sync, f)
    +
    +    def test_bogus_yield(self):
    +        @gen.coroutine
    +        def f():
    +            yield 42
    +
    +        self.assertRaises(gen.BadYieldError, self.io_loop.run_sync, f)
    +
    +    def test_bogus_yield_tuple(self):
    +        @gen.coroutine
    +        def f():
    +            yield (1, 2)
    +
    +        self.assertRaises(gen.BadYieldError, self.io_loop.run_sync, f)
    +
    +    def test_reuse(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +
    +        self.io_loop.run_sync(f)
    +        self.io_loop.run_sync(f)
    +
    +    def test_none(self):
    +        @gen.coroutine
    +        def f():
    +            yield None
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_multi(self):
    +        @gen.coroutine
    +        def f():
    +            results = yield [self.add_one_async(1), self.add_one_async(2)]
    +            self.assertEqual(results, [2, 3])
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_multi_dict(self):
    +        @gen.coroutine
    +        def f():
    +            results = yield dict(foo=self.add_one_async(1), bar=self.add_one_async(2))
    +            self.assertEqual(results, dict(foo=2, bar=3))
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_multi_delayed(self):
    +        @gen.coroutine
    +        def f():
    +            # callbacks run at different times
    +            responses = yield gen.multi_future(
    +                [self.delay(3, "v1"), self.delay(1, "v2")]
    +            )
    +            self.assertEqual(responses, ["v1", "v2"])
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_multi_dict_delayed(self):
    +        @gen.coroutine
    +        def f():
    +            # callbacks run at different times
    +            responses = yield gen.multi_future(
    +                dict(foo=self.delay(3, "v1"), bar=self.delay(1, "v2"))
    +            )
    +            self.assertEqual(responses, dict(foo="v1", bar="v2"))
    +
    +        self.io_loop.run_sync(f)
    +
    +    @skipOnTravis
    +    @gen_test
    +    def test_multi_performance(self):
    +        # Yielding a list used to have quadratic performance; make
    +        # sure a large list stays reasonable.  On my laptop a list of
    +        # 2000 used to take 1.8s, now it takes 0.12.
    +        start = time.time()
    +        yield [gen.moment for i in range(2000)]
    +        end = time.time()
    +        self.assertLess(end - start, 1.0)
    +
    +    @gen_test
    +    def test_multi_empty(self):
    +        # Empty lists or dicts should return the same type.
    +        x = yield []
    +        self.assertTrue(isinstance(x, list))
    +        y = yield {}
    +        self.assertTrue(isinstance(y, dict))
    +
    +    @gen_test
    +    def test_future(self):
    +        result = yield self.async_future(1)
    +        self.assertEqual(result, 1)
    +
    +    @gen_test
    +    def test_multi_future(self):
    +        results = yield [self.async_future(1), self.async_future(2)]
    +        self.assertEqual(results, [1, 2])
    +
    +    @gen_test
    +    def test_multi_future_duplicate(self):
    +        # Note that this doesn't work with native corotines, only with
    +        # decorated coroutines.
    +        f = self.async_future(2)
    +        results = yield [self.async_future(1), f, self.async_future(3), f]
    +        self.assertEqual(results, [1, 2, 3, 2])
    +
    +    @gen_test
    +    def test_multi_dict_future(self):
    +        results = yield dict(foo=self.async_future(1), bar=self.async_future(2))
    +        self.assertEqual(results, dict(foo=1, bar=2))
    +
    +    @gen_test
    +    def test_multi_exceptions(self):
    +        with ExpectLog(app_log, "Multiple exceptions in yield list"):
    +            with self.assertRaises(RuntimeError) as cm:
    +                yield gen.Multi(
    +                    [
    +                        self.async_exception(RuntimeError("error 1")),
    +                        self.async_exception(RuntimeError("error 2")),
    +                    ]
    +                )
    +        self.assertEqual(str(cm.exception), "error 1")
    +
    +        # With only one exception, no error is logged.
    +        with self.assertRaises(RuntimeError):
    +            yield gen.Multi(
    +                [self.async_exception(RuntimeError("error 1")), self.async_future(2)]
    +            )
    +
    +        # Exception logging may be explicitly quieted.
    +        with self.assertRaises(RuntimeError):
    +            yield gen.Multi(
    +                [
    +                    self.async_exception(RuntimeError("error 1")),
    +                    self.async_exception(RuntimeError("error 2")),
    +                ],
    +                quiet_exceptions=RuntimeError,
    +            )
    +
    +    @gen_test
    +    def test_multi_future_exceptions(self):
    +        with ExpectLog(app_log, "Multiple exceptions in yield list"):
    +            with self.assertRaises(RuntimeError) as cm:
    +                yield [
    +                    self.async_exception(RuntimeError("error 1")),
    +                    self.async_exception(RuntimeError("error 2")),
    +                ]
    +        self.assertEqual(str(cm.exception), "error 1")
    +
    +        # With only one exception, no error is logged.
    +        with self.assertRaises(RuntimeError):
    +            yield [self.async_exception(RuntimeError("error 1")), self.async_future(2)]
    +
    +        # Exception logging may be explicitly quieted.
    +        with self.assertRaises(RuntimeError):
    +            yield gen.multi_future(
    +                [
    +                    self.async_exception(RuntimeError("error 1")),
    +                    self.async_exception(RuntimeError("error 2")),
    +                ],
    +                quiet_exceptions=RuntimeError,
    +            )
    +
    +    def test_sync_raise_return(self):
    +        @gen.coroutine
    +        def f():
    +            raise gen.Return()
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_async_raise_return(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            raise gen.Return()
    +
    +        self.io_loop.run_sync(f)
    +
    +    def test_sync_raise_return_value(self):
    +        @gen.coroutine
    +        def f():
    +            raise gen.Return(42)
    +
    +        self.assertEqual(42, self.io_loop.run_sync(f))
    +
    +    def test_sync_raise_return_value_tuple(self):
    +        @gen.coroutine
    +        def f():
    +            raise gen.Return((1, 2))
    +
    +        self.assertEqual((1, 2), self.io_loop.run_sync(f))
    +
    +    def test_async_raise_return_value(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            raise gen.Return(42)
    +
    +        self.assertEqual(42, self.io_loop.run_sync(f))
    +
    +    def test_async_raise_return_value_tuple(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            raise gen.Return((1, 2))
    +
    +        self.assertEqual((1, 2), self.io_loop.run_sync(f))
    +
    +
    +class GenCoroutineTest(AsyncTestCase):
    +    def setUp(self):
    +        # Stray StopIteration exceptions can lead to tests exiting prematurely,
    +        # so we need explicit checks here to make sure the tests run all
    +        # the way through.
    +        self.finished = False
    +        super(GenCoroutineTest, self).setUp()
    +
    +    def tearDown(self):
    +        super(GenCoroutineTest, self).tearDown()
    +        assert self.finished
    +
    +    def test_attributes(self):
    +        self.finished = True
    +
    +        def f():
    +            yield gen.moment
    +
    +        coro = gen.coroutine(f)
    +        self.assertEqual(coro.__name__, f.__name__)
    +        self.assertEqual(coro.__module__, f.__module__)
    +        self.assertIs(coro.__wrapped__, f)  # type: ignore
    +
    +    def test_is_coroutine_function(self):
    +        self.finished = True
    +
    +        def f():
    +            yield gen.moment
    +
    +        coro = gen.coroutine(f)
    +        self.assertFalse(gen.is_coroutine_function(f))
    +        self.assertTrue(gen.is_coroutine_function(coro))
    +        self.assertFalse(gen.is_coroutine_function(coro()))
    +
    +    @gen_test
    +    def test_sync_gen_return(self):
    +        @gen.coroutine
    +        def f():
    +            raise gen.Return(42)
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_gen_return(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            raise gen.Return(42)
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_sync_return(self):
    +        @gen.coroutine
    +        def f():
    +            return 42
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_return(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            return 42
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_early_return(self):
    +        # A yield statement exists but is not executed, which means
    +        # this function "returns" via an exception.  This exception
    +        # doesn't happen before the exception handling is set up.
    +        @gen.coroutine
    +        def f():
    +            if True:
    +                return 42
    +            yield gen.Task(self.io_loop.add_callback)
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_await(self):
    +        @gen.coroutine
    +        def f1():
    +            yield gen.moment
    +            raise gen.Return(42)
    +
    +        # This test verifies that an async function can await a
    +        # yield-based gen.coroutine, and that a gen.coroutine
    +        # (the test method itself) can yield an async function.
    +        async def f2():
    +            result = await f1()
    +            return result
    +
    +        result = yield f2()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_asyncio_sleep_zero(self):
    +        # asyncio.sleep(0) turns into a special case (equivalent to
    +        # `yield None`)
    +        async def f():
    +            import asyncio
    +
    +            await asyncio.sleep(0)
    +            return 42
    +
    +        result = yield f()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_await_mixed_multi_native_future(self):
    +        @gen.coroutine
    +        def f1():
    +            yield gen.moment
    +
    +        async def f2():
    +            await f1()
    +            return 42
    +
    +        @gen.coroutine
    +        def f3():
    +            yield gen.moment
    +            raise gen.Return(43)
    +
    +        results = yield [f2(), f3()]
    +        self.assertEqual(results, [42, 43])
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_with_timeout(self):
    +        async def f1():
    +            return 42
    +
    +        result = yield gen.with_timeout(datetime.timedelta(hours=1), f1())
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_sync_return_no_value(self):
    +        @gen.coroutine
    +        def f():
    +            return
    +
    +        result = yield f()
    +        self.assertEqual(result, None)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_return_no_value(self):
    +        # Without a return value we don't need python 3.3.
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            return
    +
    +        result = yield f()
    +        self.assertEqual(result, None)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_sync_raise(self):
    +        @gen.coroutine
    +        def f():
    +            1 / 0
    +
    +        # The exception is raised when the future is yielded
    +        # (or equivalently when its result method is called),
    +        # not when the function itself is called).
    +        future = f()
    +        with self.assertRaises(ZeroDivisionError):
    +            yield future
    +        self.finished = True
    +
    +    @gen_test
    +    def test_async_raise(self):
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            1 / 0
    +
    +        future = f()
    +        with self.assertRaises(ZeroDivisionError):
    +            yield future
    +        self.finished = True
    +
    +    @gen_test
    +    def test_replace_yieldpoint_exception(self):
    +        # Test exception handling: a coroutine can catch one exception
    +        # raised by a yield point and raise a different one.
    +        @gen.coroutine
    +        def f1():
    +            1 / 0
    +
    +        @gen.coroutine
    +        def f2():
    +            try:
    +                yield f1()
    +            except ZeroDivisionError:
    +                raise KeyError()
    +
    +        future = f2()
    +        with self.assertRaises(KeyError):
    +            yield future
    +        self.finished = True
    +
    +    @gen_test
    +    def test_swallow_yieldpoint_exception(self):
    +        # Test exception handling: a coroutine can catch an exception
    +        # raised by a yield point and not raise a different one.
    +        @gen.coroutine
    +        def f1():
    +            1 / 0
    +
    +        @gen.coroutine
    +        def f2():
    +            try:
    +                yield f1()
    +            except ZeroDivisionError:
    +                raise gen.Return(42)
    +
    +        result = yield f2()
    +        self.assertEqual(result, 42)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_moment(self):
    +        calls = []
    +
    +        @gen.coroutine
    +        def f(name, yieldable):
    +            for i in range(5):
    +                calls.append(name)
    +                yield yieldable
    +
    +        # First, confirm the behavior without moment: each coroutine
    +        # monopolizes the event loop until it finishes.
    +        immediate = Future()  # type: Future[None]
    +        immediate.set_result(None)
    +        yield [f("a", immediate), f("b", immediate)]
    +        self.assertEqual("".join(calls), "aaaaabbbbb")
    +
    +        # With moment, they take turns.
    +        calls = []
    +        yield [f("a", gen.moment), f("b", gen.moment)]
    +        self.assertEqual("".join(calls), "ababababab")
    +        self.finished = True
    +
    +        calls = []
    +        yield [f("a", gen.moment), f("b", immediate)]
    +        self.assertEqual("".join(calls), "abbbbbaaaa")
    +
    +    @gen_test
    +    def test_sleep(self):
    +        yield gen.sleep(0.01)
    +        self.finished = True
    +
    +    @gen_test
    +    def test_py3_leak_exception_context(self):
    +        class LeakedException(Exception):
    +            pass
    +
    +        @gen.coroutine
    +        def inner(iteration):
    +            raise LeakedException(iteration)
    +
    +        try:
    +            yield inner(1)
    +        except LeakedException as e:
    +            self.assertEqual(str(e), "1")
    +            self.assertIsNone(e.__context__)
    +
    +        try:
    +            yield inner(2)
    +        except LeakedException as e:
    +            self.assertEqual(str(e), "2")
    +            self.assertIsNone(e.__context__)
    +
    +        self.finished = True
    +
    +    @skipNotCPython
    +    @unittest.skipIf(
    +        (3,) < sys.version_info < (3, 6), "asyncio.Future has reference cycles"
    +    )
    +    def test_coroutine_refcounting(self):
    +        # On CPython, tasks and their arguments should be released immediately
    +        # without waiting for garbage collection.
    +        @gen.coroutine
    +        def inner():
    +            class Foo(object):
    +                pass
    +
    +            local_var = Foo()
    +            self.local_ref = weakref.ref(local_var)
    +
    +            def dummy():
    +                pass
    +
    +            yield gen.coroutine(dummy)()
    +            raise ValueError("Some error")
    +
    +        @gen.coroutine
    +        def inner2():
    +            try:
    +                yield inner()
    +            except ValueError:
    +                pass
    +
    +        self.io_loop.run_sync(inner2, timeout=3)
    +
    +        self.assertIs(self.local_ref(), None)
    +        self.finished = True
    +
    +    def test_asyncio_future_debug_info(self):
    +        self.finished = True
    +        # Enable debug mode
    +        asyncio_loop = asyncio.get_event_loop()
    +        self.addCleanup(asyncio_loop.set_debug, asyncio_loop.get_debug())
    +        asyncio_loop.set_debug(True)
    +
    +        def f():
    +            yield gen.moment
    +
    +        coro = gen.coroutine(f)()
    +        self.assertIsInstance(coro, asyncio.Future)
    +        # We expect the coroutine repr() to show the place where
    +        # it was instantiated
    +        expected = "created at %s:%d" % (__file__, f.__code__.co_firstlineno + 3)
    +        actual = repr(coro)
    +        self.assertIn(expected, actual)
    +
    +    @gen_test
    +    def test_asyncio_gather(self):
    +        # This demonstrates that tornado coroutines can be understood
    +        # by asyncio (This failed prior to Tornado 5.0).
    +        @gen.coroutine
    +        def f():
    +            yield gen.moment
    +            raise gen.Return(1)
    +
    +        ret = yield asyncio.gather(f(), f())
    +        self.assertEqual(ret, [1, 1])
    +        self.finished = True
    +
    +
    +class GenCoroutineSequenceHandler(RequestHandler):
    +    @gen.coroutine
    +    def get(self):
    +        yield gen.moment
    +        self.write("1")
    +        yield gen.moment
    +        self.write("2")
    +        yield gen.moment
    +        self.finish("3")
    +
    +
    +class GenCoroutineUnfinishedSequenceHandler(RequestHandler):
    +    @gen.coroutine
    +    def get(self):
    +        yield gen.moment
    +        self.write("1")
    +        yield gen.moment
    +        self.write("2")
    +        yield gen.moment
    +        # just write, don't finish
    +        self.write("3")
    +
    +
    +# "Undecorated" here refers to the absence of @asynchronous.
    +class UndecoratedCoroutinesHandler(RequestHandler):
    +    @gen.coroutine
    +    def prepare(self):
    +        self.chunks = []  # type: List[str]
    +        yield gen.moment
    +        self.chunks.append("1")
    +
    +    @gen.coroutine
    +    def get(self):
    +        self.chunks.append("2")
    +        yield gen.moment
    +        self.chunks.append("3")
    +        yield gen.moment
    +        self.write("".join(self.chunks))
    +
    +
    +class AsyncPrepareErrorHandler(RequestHandler):
    +    @gen.coroutine
    +    def prepare(self):
    +        yield gen.moment
    +        raise HTTPError(403)
    +
    +    def get(self):
    +        self.finish("ok")
    +
    +
    +class NativeCoroutineHandler(RequestHandler):
    +    async def get(self):
    +        await asyncio.sleep(0)
    +        self.write("ok")
    +
    +
    +class GenWebTest(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application(
    +            [
    +                ("/coroutine_sequence", GenCoroutineSequenceHandler),
    +                (
    +                    "/coroutine_unfinished_sequence",
    +                    GenCoroutineUnfinishedSequenceHandler,
    +                ),
    +                ("/undecorated_coroutine", UndecoratedCoroutinesHandler),
    +                ("/async_prepare_error", AsyncPrepareErrorHandler),
    +                ("/native_coroutine", NativeCoroutineHandler),
    +            ]
    +        )
    +
    +    def test_coroutine_sequence_handler(self):
    +        response = self.fetch("/coroutine_sequence")
    +        self.assertEqual(response.body, b"123")
    +
    +    def test_coroutine_unfinished_sequence_handler(self):
    +        response = self.fetch("/coroutine_unfinished_sequence")
    +        self.assertEqual(response.body, b"123")
    +
    +    def test_undecorated_coroutines(self):
    +        response = self.fetch("/undecorated_coroutine")
    +        self.assertEqual(response.body, b"123")
    +
    +    def test_async_prepare_error_handler(self):
    +        response = self.fetch("/async_prepare_error")
    +        self.assertEqual(response.code, 403)
    +
    +    def test_native_coroutine_handler(self):
    +        response = self.fetch("/native_coroutine")
    +        self.assertEqual(response.code, 200)
    +        self.assertEqual(response.body, b"ok")
    +
    +
    +class WithTimeoutTest(AsyncTestCase):
    +    @gen_test
    +    def test_timeout(self):
    +        with self.assertRaises(gen.TimeoutError):
    +            yield gen.with_timeout(datetime.timedelta(seconds=0.1), Future())
    +
    +    @gen_test
    +    def test_completes_before_timeout(self):
    +        future = Future()  # type: Future[str]
    +        self.io_loop.add_timeout(
    +            datetime.timedelta(seconds=0.1), lambda: future.set_result("asdf")
    +        )
    +        result = yield gen.with_timeout(datetime.timedelta(seconds=3600), future)
    +        self.assertEqual(result, "asdf")
    +
    +    @gen_test
    +    def test_fails_before_timeout(self):
    +        future = Future()  # type: Future[str]
    +        self.io_loop.add_timeout(
    +            datetime.timedelta(seconds=0.1),
    +            lambda: future.set_exception(ZeroDivisionError()),
    +        )
    +        with self.assertRaises(ZeroDivisionError):
    +            yield gen.with_timeout(datetime.timedelta(seconds=3600), future)
    +
    +    @gen_test
    +    def test_already_resolved(self):
    +        future = Future()  # type: Future[str]
    +        future.set_result("asdf")
    +        result = yield gen.with_timeout(datetime.timedelta(seconds=3600), future)
    +        self.assertEqual(result, "asdf")
    +
    +    @gen_test
    +    def test_timeout_concurrent_future(self):
    +        # A concurrent future that does not resolve before the timeout.
    +        with futures.ThreadPoolExecutor(1) as executor:
    +            with self.assertRaises(gen.TimeoutError):
    +                yield gen.with_timeout(
    +                    self.io_loop.time(), executor.submit(time.sleep, 0.1)
    +                )
    +
    +    @gen_test
    +    def test_completed_concurrent_future(self):
    +        # A concurrent future that is resolved before we even submit it
    +        # to with_timeout.
    +        with futures.ThreadPoolExecutor(1) as executor:
    +
    +            def dummy():
    +                pass
    +
    +            f = executor.submit(dummy)
    +            f.result()  # wait for completion
    +            yield gen.with_timeout(datetime.timedelta(seconds=3600), f)
    +
    +    @gen_test
    +    def test_normal_concurrent_future(self):
    +        # A conccurrent future that resolves while waiting for the timeout.
    +        with futures.ThreadPoolExecutor(1) as executor:
    +            yield gen.with_timeout(
    +                datetime.timedelta(seconds=3600),
    +                executor.submit(lambda: time.sleep(0.01)),
    +            )
    +
    +
    +class WaitIteratorTest(AsyncTestCase):
    +    @gen_test
    +    def test_empty_iterator(self):
    +        g = gen.WaitIterator()
    +        self.assertTrue(g.done(), "empty generator iterated")
    +
    +        with self.assertRaises(ValueError):
    +            g = gen.WaitIterator(Future(), bar=Future())
    +
    +        self.assertEqual(g.current_index, None, "bad nil current index")
    +        self.assertEqual(g.current_future, None, "bad nil current future")
    +
    +    @gen_test
    +    def test_already_done(self):
    +        f1 = Future()  # type: Future[int]
    +        f2 = Future()  # type: Future[int]
    +        f3 = Future()  # type: Future[int]
    +        f1.set_result(24)
    +        f2.set_result(42)
    +        f3.set_result(84)
    +
    +        g = gen.WaitIterator(f1, f2, f3)
    +        i = 0
    +        while not g.done():
    +            r = yield g.next()
    +            # Order is not guaranteed, but the current implementation
    +            # preserves ordering of already-done Futures.
    +            if i == 0:
    +                self.assertEqual(g.current_index, 0)
    +                self.assertIs(g.current_future, f1)
    +                self.assertEqual(r, 24)
    +            elif i == 1:
    +                self.assertEqual(g.current_index, 1)
    +                self.assertIs(g.current_future, f2)
    +                self.assertEqual(r, 42)
    +            elif i == 2:
    +                self.assertEqual(g.current_index, 2)
    +                self.assertIs(g.current_future, f3)
    +                self.assertEqual(r, 84)
    +            i += 1
    +
    +        self.assertEqual(g.current_index, None, "bad nil current index")
    +        self.assertEqual(g.current_future, None, "bad nil current future")
    +
    +        dg = gen.WaitIterator(f1=f1, f2=f2)
    +
    +        while not dg.done():
    +            dr = yield dg.next()
    +            if dg.current_index == "f1":
    +                self.assertTrue(
    +                    dg.current_future == f1 and dr == 24,
    +                    "WaitIterator dict status incorrect",
    +                )
    +            elif dg.current_index == "f2":
    +                self.assertTrue(
    +                    dg.current_future == f2 and dr == 42,
    +                    "WaitIterator dict status incorrect",
    +                )
    +            else:
    +                self.fail("got bad WaitIterator index {}".format(dg.current_index))
    +
    +            i += 1
    +
    +        self.assertEqual(dg.current_index, None, "bad nil current index")
    +        self.assertEqual(dg.current_future, None, "bad nil current future")
    +
    +    def finish_coroutines(self, iteration, futures):
    +        if iteration == 3:
    +            futures[2].set_result(24)
    +        elif iteration == 5:
    +            futures[0].set_exception(ZeroDivisionError())
    +        elif iteration == 8:
    +            futures[1].set_result(42)
    +            futures[3].set_result(84)
    +
    +        if iteration < 8:
    +            self.io_loop.add_callback(self.finish_coroutines, iteration + 1, futures)
    +
    +    @gen_test
    +    def test_iterator(self):
    +        futures = [Future(), Future(), Future(), Future()]  # type: List[Future[int]]
    +
    +        self.finish_coroutines(0, futures)
    +
    +        g = gen.WaitIterator(*futures)
    +
    +        i = 0
    +        while not g.done():
    +            try:
    +                r = yield g.next()
    +            except ZeroDivisionError:
    +                self.assertIs(g.current_future, futures[0], "exception future invalid")
    +            else:
    +                if i == 0:
    +                    self.assertEqual(r, 24, "iterator value incorrect")
    +                    self.assertEqual(g.current_index, 2, "wrong index")
    +                elif i == 2:
    +                    self.assertEqual(r, 42, "iterator value incorrect")
    +                    self.assertEqual(g.current_index, 1, "wrong index")
    +                elif i == 3:
    +                    self.assertEqual(r, 84, "iterator value incorrect")
    +                    self.assertEqual(g.current_index, 3, "wrong index")
    +            i += 1
    +
    +    @gen_test
    +    def test_iterator_async_await(self):
    +        # Recreate the previous test with py35 syntax. It's a little clunky
    +        # because of the way the previous test handles an exception on
    +        # a single iteration.
    +        futures = [Future(), Future(), Future(), Future()]  # type: List[Future[int]]
    +        self.finish_coroutines(0, futures)
    +        self.finished = False
    +
    +        async def f():
    +            i = 0
    +            g = gen.WaitIterator(*futures)
    +            try:
    +                async for r in g:
    +                    if i == 0:
    +                        self.assertEqual(r, 24, "iterator value incorrect")
    +                        self.assertEqual(g.current_index, 2, "wrong index")
    +                    else:
    +                        raise Exception("expected exception on iteration 1")
    +                    i += 1
    +            except ZeroDivisionError:
    +                i += 1
    +            async for r in g:
    +                if i == 2:
    +                    self.assertEqual(r, 42, "iterator value incorrect")
    +                    self.assertEqual(g.current_index, 1, "wrong index")
    +                elif i == 3:
    +                    self.assertEqual(r, 84, "iterator value incorrect")
    +                    self.assertEqual(g.current_index, 3, "wrong index")
    +                else:
    +                    raise Exception("didn't expect iteration %d" % i)
    +                i += 1
    +            self.finished = True
    +
    +        yield f()
    +        self.assertTrue(self.finished)
    +
    +    @gen_test
    +    def test_no_ref(self):
    +        # In this usage, there is no direct hard reference to the
    +        # WaitIterator itself, only the Future it returns. Since
    +        # WaitIterator uses weak references internally to improve GC
    +        # performance, this used to cause problems.
    +        yield gen.with_timeout(
    +            datetime.timedelta(seconds=0.1), gen.WaitIterator(gen.sleep(0)).next()
    +        )
    +
    +
    +class RunnerGCTest(AsyncTestCase):
    +    def is_pypy3(self):
    +        return platform.python_implementation() == "PyPy" and sys.version_info > (3,)
    +
    +    @gen_test
    +    def test_gc(self):
    +        # Github issue 1769: Runner objects can get GCed unexpectedly
    +        # while their future is alive.
    +        weakref_scope = [None]  # type: List[Optional[weakref.ReferenceType]]
    +
    +        def callback():
    +            gc.collect(2)
    +            weakref_scope[0]().set_result(123)  # type: ignore
    +
    +        @gen.coroutine
    +        def tester():
    +            fut = Future()  # type: Future[int]
    +            weakref_scope[0] = weakref.ref(fut)
    +            self.io_loop.add_callback(callback)
    +            yield fut
    +
    +        yield gen.with_timeout(datetime.timedelta(seconds=0.2), tester())
    +
    +    def test_gc_infinite_coro(self):
    +        # Github issue 2229: suspended coroutines should be GCed when
    +        # their loop is closed, even if they're involved in a reference
    +        # cycle.
    +        loop = self.get_new_ioloop()
    +        result = []  # type: List[Optional[bool]]
    +        wfut = []
    +
    +        @gen.coroutine
    +        def infinite_coro():
    +            try:
    +                while True:
    +                    yield gen.sleep(1e-3)
    +                    result.append(True)
    +            finally:
    +                # coroutine finalizer
    +                result.append(None)
    +
    +        @gen.coroutine
    +        def do_something():
    +            fut = infinite_coro()
    +            fut._refcycle = fut  # type: ignore
    +            wfut.append(weakref.ref(fut))
    +            yield gen.sleep(0.2)
    +
    +        loop.run_sync(do_something)
    +        loop.close()
    +        gc.collect()
    +        # Future was collected
    +        self.assertIs(wfut[0](), None)
    +        # At least one wakeup
    +        self.assertGreaterEqual(len(result), 2)
    +        if not self.is_pypy3():
    +            # coroutine finalizer was called (not on PyPy3 apparently)
    +            self.assertIs(result[-1], None)
    +
    +    def test_gc_infinite_async_await(self):
    +        # Same as test_gc_infinite_coro, but with a `async def` function
    +        import asyncio
    +
    +        async def infinite_coro(result):
    +            try:
    +                while True:
    +                    await gen.sleep(1e-3)
    +                    result.append(True)
    +            finally:
    +                # coroutine finalizer
    +                result.append(None)
    +
    +        loop = self.get_new_ioloop()
    +        result = []  # type: List[Optional[bool]]
    +        wfut = []
    +
    +        @gen.coroutine
    +        def do_something():
    +            fut = asyncio.get_event_loop().create_task(infinite_coro(result))
    +            fut._refcycle = fut  # type: ignore
    +            wfut.append(weakref.ref(fut))
    +            yield gen.sleep(0.2)
    +
    +        loop.run_sync(do_something)
    +        with ExpectLog("asyncio", "Task was destroyed but it is pending"):
    +            loop.close()
    +            gc.collect()
    +        # Future was collected
    +        self.assertIs(wfut[0](), None)
    +        # At least one wakeup and one finally
    +        self.assertGreaterEqual(len(result), 2)
    +        if not self.is_pypy3():
    +            # coroutine finalizer was called (not on PyPy3 apparently)
    +            self.assertIs(result[-1], None)
    +
    +    def test_multi_moment(self):
    +        # Test gen.multi with moment
    +        # now that it's not a real Future
    +        @gen.coroutine
    +        def wait_a_moment():
    +            result = yield gen.multi([gen.moment, gen.moment])
    +            raise gen.Return(result)
    +
    +        loop = self.get_new_ioloop()
    +        result = loop.run_sync(wait_a_moment)
    +        self.assertEqual(result, [None, None])
    +
    +
    +if contextvars is not None:
    +    ctx_var = contextvars.ContextVar("ctx_var")  # type: contextvars.ContextVar[int]
    +
    +
    +@unittest.skipIf(contextvars is None, "contextvars module not present")
    +class ContextVarsTest(AsyncTestCase):
    +    async def native_root(self, x):
    +        ctx_var.set(x)
    +        await self.inner(x)
    +
    +    @gen.coroutine
    +    def gen_root(self, x):
    +        ctx_var.set(x)
    +        yield
    +        yield self.inner(x)
    +
    +    async def inner(self, x):
    +        self.assertEqual(ctx_var.get(), x)
    +        await self.gen_inner(x)
    +        self.assertEqual(ctx_var.get(), x)
    +
    +        # IOLoop.run_in_executor doesn't automatically copy context
    +        ctx = contextvars.copy_context()
    +        await self.io_loop.run_in_executor(None, lambda: ctx.run(self.thread_inner, x))
    +        self.assertEqual(ctx_var.get(), x)
    +
    +        # Neither does asyncio's run_in_executor.
    +        await asyncio.get_event_loop().run_in_executor(
    +            None, lambda: ctx.run(self.thread_inner, x)
    +        )
    +        self.assertEqual(ctx_var.get(), x)
    +
    +    @gen.coroutine
    +    def gen_inner(self, x):
    +        self.assertEqual(ctx_var.get(), x)
    +        yield
    +        self.assertEqual(ctx_var.get(), x)
    +
    +    def thread_inner(self, x):
    +        self.assertEqual(ctx_var.get(), x)
    +
    +    @gen_test
    +    def test_propagate(self):
    +        # Verify that context vars get propagated across various
    +        # combinations of native and decorated coroutines.
    +        yield [
    +            self.native_root(1),
    +            self.native_root(2),
    +            self.gen_root(3),
    +            self.gen_root(4),
    +        ]
    +
    +
    +if __name__ == "__main__":
    +    unittest.main()
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.mo b/venv/lib/python3.7/site-packages/tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.mo
    new file mode 100644
    index 0000000000000000000000000000000000000000..a97bf9c57460ecfc27761accf90d712ea5cebb44
    GIT binary patch
    literal 665
    zcmYk3PjAyO7{(2hf8hd1hzkcFL6b*PG%=;?w5-jxh@`2~Zqm59iI;kKc4a#Z&~AJN
    zz5pk7=gei_VK?{)ddc_y6;;R;R4AB&o;|(*{A@;`klngX$w<1GoS%|xSutEHQbYJ5j
    z2>p#U|CR4UkQD4acQ0S&j^n5xSx$x#KFGr?S$mt0VlSn}lBuTB2x^rM@!nyY;!%{v
    zcq`7LB;ARI!y=wcwjnC(hSrQs89fVe8jbc3-N;*Mx+C~H{DoBpM$M8eUVUG%?t23z
    zEt9a_#|6x7*$4Y_At;wUT+XRB%=R05LN-@9H`WQ$B$lPBxU56GIpfwFi$+sH_LM#|
    zA(o5w*7UnQ{MYuMOT3MP7d;ONhG(2fr, YEAR.
    +#
    +#, fuzzy
    +msgid ""
    +msgstr ""
    +"Project-Id-Version: PACKAGE VERSION\n"
    +"Report-Msgid-Bugs-To: \n"
    +"POT-Creation-Date: 2015-01-27 11:05+0300\n"
    +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    +"Last-Translator: FULL NAME \n"
    +"Language-Team: LANGUAGE \n"
    +"Language: \n"
    +"MIME-Version: 1.0\n"
    +"Content-Type: text/plain; charset=utf-8\n"
    +"Content-Transfer-Encoding: 8bit\n"
    +"Plural-Forms: nplurals=2; plural=(n > 1);\n"
    +
    +#: extract_me.py:11
    +msgid "school"
    +msgstr "école"
    +
    +#: extract_me.py:12
    +msgctxt "law"
    +msgid "right"
    +msgstr "le droit"
    +
    +#: extract_me.py:13
    +msgctxt "good"
    +msgid "right"
    +msgstr "le bien"
    +
    +#: extract_me.py:14
    +msgctxt "organization"
    +msgid "club"
    +msgid_plural "clubs"
    +msgstr[0] "le club"
    +msgstr[1] "les clubs"
    +
    +#: extract_me.py:15
    +msgctxt "stick"
    +msgid "club"
    +msgid_plural "clubs"
    +msgstr[0] "le bâton"
    +msgstr[1] "les bâtons"
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/http1connection_test.py b/venv/lib/python3.7/site-packages/tornado/test/http1connection_test.py
    new file mode 100644
    index 0000000..023c02a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/http1connection_test.py
    @@ -0,0 +1,58 @@
    +import socket
    +
    +from tornado.http1connection import HTTP1Connection
    +from tornado.httputil import HTTPMessageDelegate
    +from tornado.iostream import IOStream
    +from tornado.locks import Event
    +from tornado.netutil import add_accept_handler
    +from tornado.testing import AsyncTestCase, bind_unused_port, gen_test
    +
    +
    +class HTTP1ConnectionTest(AsyncTestCase):
    +    def setUp(self):
    +        super(HTTP1ConnectionTest, self).setUp()
    +        self.asyncSetUp()
    +
    +    @gen_test
    +    def asyncSetUp(self):
    +        listener, port = bind_unused_port()
    +        event = Event()
    +
    +        def accept_callback(conn, addr):
    +            self.server_stream = IOStream(conn)
    +            self.addCleanup(self.server_stream.close)
    +            event.set()
    +
    +        add_accept_handler(listener, accept_callback)
    +        self.client_stream = IOStream(socket.socket())
    +        self.addCleanup(self.client_stream.close)
    +        yield [self.client_stream.connect(("127.0.0.1", port)), event.wait()]
    +        self.io_loop.remove_handler(listener)
    +        listener.close()
    +
    +    @gen_test
    +    def test_http10_no_content_length(self):
    +        # Regression test for a bug in which can_keep_alive would crash
    +        # for an HTTP/1.0 (not 1.1) response with no content-length.
    +        conn = HTTP1Connection(self.client_stream, True)
    +        self.server_stream.write(b"HTTP/1.0 200 Not Modified\r\n\r\nhello")
    +        self.server_stream.close()
    +
    +        event = Event()
    +        test = self
    +        body = []
    +
    +        class Delegate(HTTPMessageDelegate):
    +            def headers_received(self, start_line, headers):
    +                test.code = start_line.code
    +
    +            def data_received(self, data):
    +                body.append(data)
    +
    +            def finish(self):
    +                event.set()
    +
    +        yield conn.read_response(Delegate())
    +        yield event.wait()
    +        self.assertEqual(self.code, 200)
    +        self.assertEqual(b"".join(body), b"hello")
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/httpclient_test.py b/venv/lib/python3.7/site-packages/tornado/test/httpclient_test.py
    new file mode 100644
    index 0000000..26a1ad7
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/httpclient_test.py
    @@ -0,0 +1,832 @@
    +# -*- coding: utf-8 -*-
    +import base64
    +import binascii
    +from contextlib import closing
    +import copy
    +import threading
    +import datetime
    +from io import BytesIO
    +import subprocess
    +import sys
    +import time
    +import typing  # noqa: F401
    +import unicodedata
    +import unittest
    +
    +from tornado.escape import utf8, native_str, to_unicode
    +from tornado import gen
    +from tornado.httpclient import (
    +    HTTPRequest,
    +    HTTPResponse,
    +    _RequestProxy,
    +    HTTPError,
    +    HTTPClient,
    +)
    +from tornado.httpserver import HTTPServer
    +from tornado.ioloop import IOLoop
    +from tornado.iostream import IOStream
    +from tornado.log import gen_log, app_log
    +from tornado import netutil
    +from tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog
    +from tornado.test.util import skipOnTravis
    +from tornado.web import Application, RequestHandler, url
    +from tornado.httputil import format_timestamp, HTTPHeaders
    +
    +
    +class HelloWorldHandler(RequestHandler):
    +    def get(self):
    +        name = self.get_argument("name", "world")
    +        self.set_header("Content-Type", "text/plain")
    +        self.finish("Hello %s!" % name)
    +
    +
    +class PostHandler(RequestHandler):
    +    def post(self):
    +        self.finish(
    +            "Post arg1: %s, arg2: %s"
    +            % (self.get_argument("arg1"), self.get_argument("arg2"))
    +        )
    +
    +
    +class PutHandler(RequestHandler):
    +    def put(self):
    +        self.write("Put body: ")
    +        self.write(self.request.body)
    +
    +
    +class RedirectHandler(RequestHandler):
    +    def prepare(self):
    +        self.write("redirects can have bodies too")
    +        self.redirect(
    +            self.get_argument("url"), status=int(self.get_argument("status", "302"))
    +        )
    +
    +
    +class RedirectWithoutLocationHandler(RequestHandler):
    +    def prepare(self):
    +        # For testing error handling of a redirect with no location header.
    +        self.set_status(301)
    +        self.finish()
    +
    +
    +class ChunkHandler(RequestHandler):
    +    @gen.coroutine
    +    def get(self):
    +        self.write("asdf")
    +        self.flush()
    +        # Wait a bit to ensure the chunks are sent and received separately.
    +        yield gen.sleep(0.01)
    +        self.write("qwer")
    +
    +
    +class AuthHandler(RequestHandler):
    +    def get(self):
    +        self.finish(self.request.headers["Authorization"])
    +
    +
    +class CountdownHandler(RequestHandler):
    +    def get(self, count):
    +        count = int(count)
    +        if count > 0:
    +            self.redirect(self.reverse_url("countdown", count - 1))
    +        else:
    +            self.write("Zero")
    +
    +
    +class EchoPostHandler(RequestHandler):
    +    def post(self):
    +        self.write(self.request.body)
    +
    +
    +class UserAgentHandler(RequestHandler):
    +    def get(self):
    +        self.write(self.request.headers.get("User-Agent", "User agent not set"))
    +
    +
    +class ContentLength304Handler(RequestHandler):
    +    def get(self):
    +        self.set_status(304)
    +        self.set_header("Content-Length", 42)
    +
    +    def _clear_headers_for_304(self):
    +        # Tornado strips content-length from 304 responses, but here we
    +        # want to simulate servers that include the headers anyway.
    +        pass
    +
    +
    +class PatchHandler(RequestHandler):
    +    def patch(self):
    +        "Return the request payload - so we can check it is being kept"
    +        self.write(self.request.body)
    +
    +
    +class AllMethodsHandler(RequestHandler):
    +    SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + ("OTHER",)  # type: ignore
    +
    +    def method(self):
    +        self.write(self.request.method)
    +
    +    get = head = post = put = delete = options = patch = other = method  # type: ignore
    +
    +
    +class SetHeaderHandler(RequestHandler):
    +    def get(self):
    +        # Use get_arguments for keys to get strings, but
    +        # request.arguments for values to get bytes.
    +        for k, v in zip(self.get_arguments("k"), self.request.arguments["v"]):
    +            self.set_header(k, v)
    +
    +
    +# These tests end up getting run redundantly: once here with the default
    +# HTTPClient implementation, and then again in each implementation's own
    +# test suite.
    +
    +
    +class HTTPClientCommonTestCase(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application(
    +            [
    +                url("/hello", HelloWorldHandler),
    +                url("/post", PostHandler),
    +                url("/put", PutHandler),
    +                url("/redirect", RedirectHandler),
    +                url("/redirect_without_location", RedirectWithoutLocationHandler),
    +                url("/chunk", ChunkHandler),
    +                url("/auth", AuthHandler),
    +                url("/countdown/([0-9]+)", CountdownHandler, name="countdown"),
    +                url("/echopost", EchoPostHandler),
    +                url("/user_agent", UserAgentHandler),
    +                url("/304_with_content_length", ContentLength304Handler),
    +                url("/all_methods", AllMethodsHandler),
    +                url("/patch", PatchHandler),
    +                url("/set_header", SetHeaderHandler),
    +            ],
    +            gzip=True,
    +        )
    +
    +    def test_patch_receives_payload(self):
    +        body = b"some patch data"
    +        response = self.fetch("/patch", method="PATCH", body=body)
    +        self.assertEqual(response.code, 200)
    +        self.assertEqual(response.body, body)
    +
    +    @skipOnTravis
    +    def test_hello_world(self):
    +        response = self.fetch("/hello")
    +        self.assertEqual(response.code, 200)
    +        self.assertEqual(response.headers["Content-Type"], "text/plain")
    +        self.assertEqual(response.body, b"Hello world!")
    +        self.assertEqual(int(response.request_time), 0)
    +
    +        response = self.fetch("/hello?name=Ben")
    +        self.assertEqual(response.body, b"Hello Ben!")
    +
    +    def test_streaming_callback(self):
    +        # streaming_callback is also tested in test_chunked
    +        chunks = []  # type: typing.List[bytes]
    +        response = self.fetch("/hello", streaming_callback=chunks.append)
    +        # with streaming_callback, data goes to the callback and not response.body
    +        self.assertEqual(chunks, [b"Hello world!"])
    +        self.assertFalse(response.body)
    +
    +    def test_post(self):
    +        response = self.fetch("/post", method="POST", body="arg1=foo&arg2=bar")
    +        self.assertEqual(response.code, 200)
    +        self.assertEqual(response.body, b"Post arg1: foo, arg2: bar")
    +
    +    def test_chunked(self):
    +        response = self.fetch("/chunk")
    +        self.assertEqual(response.body, b"asdfqwer")
    +
    +        chunks = []  # type: typing.List[bytes]
    +        response = self.fetch("/chunk", streaming_callback=chunks.append)
    +        self.assertEqual(chunks, [b"asdf", b"qwer"])
    +        self.assertFalse(response.body)
    +
    +    def test_chunked_close(self):
    +        # test case in which chunks spread read-callback processing
    +        # over several ioloop iterations, but the connection is already closed.
    +        sock, port = bind_unused_port()
    +        with closing(sock):
    +
    +            @gen.coroutine
    +            def accept_callback(conn, address):
    +                # fake an HTTP server using chunked encoding where the final chunks
    +                # and connection close all happen at once
    +                stream = IOStream(conn)
    +                request_data = yield stream.read_until(b"\r\n\r\n")
    +                if b"HTTP/1." not in request_data:
    +                    self.skipTest("requires HTTP/1.x")
    +                yield stream.write(
    +                    b"""\
    +HTTP/1.1 200 OK
    +Transfer-Encoding: chunked
    +
    +1
    +1
    +1
    +2
    +0
    +
    +""".replace(
    +                        b"\n", b"\r\n"
    +                    )
    +                )
    +                stream.close()
    +
    +            netutil.add_accept_handler(sock, accept_callback)  # type: ignore
    +            resp = self.fetch("http://127.0.0.1:%d/" % port)
    +            resp.rethrow()
    +            self.assertEqual(resp.body, b"12")
    +            self.io_loop.remove_handler(sock.fileno())
    +
    +    def test_basic_auth(self):
    +        # This test data appears in section 2 of RFC 7617.
    +        self.assertEqual(
    +            self.fetch(
    +                "/auth", auth_username="Aladdin", auth_password="open sesame"
    +            ).body,
    +            b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    +        )
    +
    +    def test_basic_auth_explicit_mode(self):
    +        self.assertEqual(
    +            self.fetch(
    +                "/auth",
    +                auth_username="Aladdin",
    +                auth_password="open sesame",
    +                auth_mode="basic",
    +            ).body,
    +            b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    +        )
    +
    +    def test_basic_auth_unicode(self):
    +        # This test data appears in section 2.1 of RFC 7617.
    +        self.assertEqual(
    +            self.fetch("/auth", auth_username="test", auth_password="123£").body,
    +            b"Basic dGVzdDoxMjPCow==",
    +        )
    +
    +        # The standard mandates NFC. Give it a decomposed username
    +        # and ensure it is normalized to composed form.
    +        username = unicodedata.normalize("NFD", u"josé")
    +        self.assertEqual(
    +            self.fetch("/auth", auth_username=username, auth_password="səcrət").body,
    +            b"Basic am9zw6k6c8mZY3LJmXQ=",
    +        )
    +
    +    def test_unsupported_auth_mode(self):
    +        # curl and simple clients handle errors a bit differently; the
    +        # important thing is that they don't fall back to basic auth
    +        # on an unknown mode.
    +        with ExpectLog(gen_log, "uncaught exception", required=False):
    +            with self.assertRaises((ValueError, HTTPError)):
    +                self.fetch(
    +                    "/auth",
    +                    auth_username="Aladdin",
    +                    auth_password="open sesame",
    +                    auth_mode="asdf",
    +                    raise_error=True,
    +                )
    +
    +    def test_follow_redirect(self):
    +        response = self.fetch("/countdown/2", follow_redirects=False)
    +        self.assertEqual(302, response.code)
    +        self.assertTrue(response.headers["Location"].endswith("/countdown/1"))
    +
    +        response = self.fetch("/countdown/2")
    +        self.assertEqual(200, response.code)
    +        self.assertTrue(response.effective_url.endswith("/countdown/0"))
    +        self.assertEqual(b"Zero", response.body)
    +
    +    def test_redirect_without_location(self):
    +        response = self.fetch("/redirect_without_location", follow_redirects=True)
    +        # If there is no location header, the redirect response should
    +        # just be returned as-is. (This should arguably raise an
    +        # error, but libcurl doesn't treat this as an error, so we
    +        # don't either).
    +        self.assertEqual(301, response.code)
    +
    +    def test_redirect_put_with_body(self):
    +        response = self.fetch(
    +            "/redirect?url=/put&status=307", method="PUT", body="hello"
    +        )
    +        self.assertEqual(response.body, b"Put body: hello")
    +
    +    def test_redirect_put_without_body(self):
    +        # This "without body" edge case is similar to what happens with body_producer.
    +        response = self.fetch(
    +            "/redirect?url=/put&status=307",
    +            method="PUT",
    +            allow_nonstandard_methods=True,
    +        )
    +        self.assertEqual(response.body, b"Put body: ")
    +
    +    def test_method_after_redirect(self):
    +        # Legacy redirect codes (301, 302) convert POST requests to GET.
    +        for status in [301, 302, 303]:
    +            url = "/redirect?url=/all_methods&status=%d" % status
    +            resp = self.fetch(url, method="POST", body=b"")
    +            self.assertEqual(b"GET", resp.body)
    +
    +            # Other methods are left alone.
    +            for method in ["GET", "OPTIONS", "PUT", "DELETE"]:
    +                resp = self.fetch(url, method=method, allow_nonstandard_methods=True)
    +                self.assertEqual(utf8(method), resp.body)
    +            # HEAD is different so check it separately.
    +            resp = self.fetch(url, method="HEAD")
    +            self.assertEqual(200, resp.code)
    +            self.assertEqual(b"", resp.body)
    +
    +        # Newer redirects always preserve the original method.
    +        for status in [307, 308]:
    +            url = "/redirect?url=/all_methods&status=307"
    +            for method in ["GET", "OPTIONS", "POST", "PUT", "DELETE"]:
    +                resp = self.fetch(url, method=method, allow_nonstandard_methods=True)
    +                self.assertEqual(method, to_unicode(resp.body))
    +            resp = self.fetch(url, method="HEAD")
    +            self.assertEqual(200, resp.code)
    +            self.assertEqual(b"", resp.body)
    +
    +    def test_credentials_in_url(self):
    +        url = self.get_url("/auth").replace("http://", "http://me:secret@")
    +        response = self.fetch(url)
    +        self.assertEqual(b"Basic " + base64.b64encode(b"me:secret"), response.body)
    +
    +    def test_body_encoding(self):
    +        unicode_body = u"\xe9"
    +        byte_body = binascii.a2b_hex(b"e9")
    +
    +        # unicode string in body gets converted to utf8
    +        response = self.fetch(
    +            "/echopost",
    +            method="POST",
    +            body=unicode_body,
    +            headers={"Content-Type": "application/blah"},
    +        )
    +        self.assertEqual(response.headers["Content-Length"], "2")
    +        self.assertEqual(response.body, utf8(unicode_body))
    +
    +        # byte strings pass through directly
    +        response = self.fetch(
    +            "/echopost",
    +            method="POST",
    +            body=byte_body,
    +            headers={"Content-Type": "application/blah"},
    +        )
    +        self.assertEqual(response.headers["Content-Length"], "1")
    +        self.assertEqual(response.body, byte_body)
    +
    +        # Mixing unicode in headers and byte string bodies shouldn't
    +        # break anything
    +        response = self.fetch(
    +            "/echopost",
    +            method="POST",
    +            body=byte_body,
    +            headers={"Content-Type": "application/blah"},
    +            user_agent=u"foo",
    +        )
    +        self.assertEqual(response.headers["Content-Length"], "1")
    +        self.assertEqual(response.body, byte_body)
    +
    +    def test_types(self):
    +        response = self.fetch("/hello")
    +        self.assertEqual(type(response.body), bytes)
    +        self.assertEqual(type(response.headers["Content-Type"]), str)
    +        self.assertEqual(type(response.code), int)
    +        self.assertEqual(type(response.effective_url), str)
    +
    +    def test_header_callback(self):
    +        first_line = []
    +        headers = {}
    +        chunks = []
    +
    +        def header_callback(header_line):
    +            if header_line.startswith("HTTP/1.1 101"):
    +                # Upgrading to HTTP/2
    +                pass
    +            elif header_line.startswith("HTTP/"):
    +                first_line.append(header_line)
    +            elif header_line != "\r\n":
    +                k, v = header_line.split(":", 1)
    +                headers[k.lower()] = v.strip()
    +
    +        def streaming_callback(chunk):
    +            # All header callbacks are run before any streaming callbacks,
    +            # so the header data is available to process the data as it
    +            # comes in.
    +            self.assertEqual(headers["content-type"], "text/html; charset=UTF-8")
    +            chunks.append(chunk)
    +
    +        self.fetch(
    +            "/chunk",
    +            header_callback=header_callback,
    +            streaming_callback=streaming_callback,
    +        )
    +        self.assertEqual(len(first_line), 1, first_line)
    +        self.assertRegexpMatches(first_line[0], "HTTP/[0-9]\\.[0-9] 200.*\r\n")
    +        self.assertEqual(chunks, [b"asdf", b"qwer"])
    +
    +    @gen_test
    +    def test_configure_defaults(self):
    +        defaults = dict(user_agent="TestDefaultUserAgent", allow_ipv6=False)
    +        # Construct a new instance of the configured client class
    +        client = self.http_client.__class__(force_instance=True, defaults=defaults)
    +        try:
    +            response = yield client.fetch(self.get_url("/user_agent"))
    +            self.assertEqual(response.body, b"TestDefaultUserAgent")
    +        finally:
    +            client.close()
    +
    +    def test_header_types(self):
    +        # Header values may be passed as character or utf8 byte strings,
    +        # in a plain dictionary or an HTTPHeaders object.
    +        # Keys must always be the native str type.
    +        # All combinations should have the same results on the wire.
    +        for value in [u"MyUserAgent", b"MyUserAgent"]:
    +            for container in [dict, HTTPHeaders]:
    +                headers = container()
    +                headers["User-Agent"] = value
    +                resp = self.fetch("/user_agent", headers=headers)
    +                self.assertEqual(
    +                    resp.body,
    +                    b"MyUserAgent",
    +                    "response=%r, value=%r, container=%r"
    +                    % (resp.body, value, container),
    +                )
    +
    +    def test_multi_line_headers(self):
    +        # Multi-line http headers are rare but rfc-allowed
    +        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
    +        sock, port = bind_unused_port()
    +        with closing(sock):
    +
    +            @gen.coroutine
    +            def accept_callback(conn, address):
    +                stream = IOStream(conn)
    +                request_data = yield stream.read_until(b"\r\n\r\n")
    +                if b"HTTP/1." not in request_data:
    +                    self.skipTest("requires HTTP/1.x")
    +                yield stream.write(
    +                    b"""\
    +HTTP/1.1 200 OK
    +X-XSS-Protection: 1;
    +\tmode=block
    +
    +""".replace(
    +                        b"\n", b"\r\n"
    +                    )
    +                )
    +                stream.close()
    +
    +            netutil.add_accept_handler(sock, accept_callback)  # type: ignore
    +            resp = self.fetch("http://127.0.0.1:%d/" % port)
    +            resp.rethrow()
    +            self.assertEqual(resp.headers["X-XSS-Protection"], "1; mode=block")
    +            self.io_loop.remove_handler(sock.fileno())
    +
    +    def test_304_with_content_length(self):
    +        # According to the spec 304 responses SHOULD NOT include
    +        # Content-Length or other entity headers, but some servers do it
    +        # anyway.
    +        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
    +        response = self.fetch("/304_with_content_length")
    +        self.assertEqual(response.code, 304)
    +        self.assertEqual(response.headers["Content-Length"], "42")
    +
    +    @gen_test
    +    def test_future_interface(self):
    +        response = yield self.http_client.fetch(self.get_url("/hello"))
    +        self.assertEqual(response.body, b"Hello world!")
    +
    +    @gen_test
    +    def test_future_http_error(self):
    +        with self.assertRaises(HTTPError) as context:
    +            yield self.http_client.fetch(self.get_url("/notfound"))
    +        self.assertEqual(context.exception.code, 404)
    +        self.assertEqual(context.exception.response.code, 404)
    +
    +    @gen_test
    +    def test_future_http_error_no_raise(self):
    +        response = yield self.http_client.fetch(
    +            self.get_url("/notfound"), raise_error=False
    +        )
    +        self.assertEqual(response.code, 404)
    +
    +    @gen_test
    +    def test_reuse_request_from_response(self):
    +        # The response.request attribute should be an HTTPRequest, not
    +        # a _RequestProxy.
    +        # This test uses self.http_client.fetch because self.fetch calls
    +        # self.get_url on the input unconditionally.
    +        url = self.get_url("/hello")
    +        response = yield self.http_client.fetch(url)
    +        self.assertEqual(response.request.url, url)
    +        self.assertTrue(isinstance(response.request, HTTPRequest))
    +        response2 = yield self.http_client.fetch(response.request)
    +        self.assertEqual(response2.body, b"Hello world!")
    +
    +    @gen_test
    +    def test_bind_source_ip(self):
    +        url = self.get_url("/hello")
    +        request = HTTPRequest(url, network_interface="127.0.0.1")
    +        response = yield self.http_client.fetch(request)
    +        self.assertEqual(response.code, 200)
    +
    +        with self.assertRaises((ValueError, HTTPError)) as context:
    +            request = HTTPRequest(url, network_interface="not-interface-or-ip")
    +            yield self.http_client.fetch(request)
    +        self.assertIn("not-interface-or-ip", str(context.exception))
    +
    +    def test_all_methods(self):
    +        for method in ["GET", "DELETE", "OPTIONS"]:
    +            response = self.fetch("/all_methods", method=method)
    +            self.assertEqual(response.body, utf8(method))
    +        for method in ["POST", "PUT", "PATCH"]:
    +            response = self.fetch("/all_methods", method=method, body=b"")
    +            self.assertEqual(response.body, utf8(method))
    +        response = self.fetch("/all_methods", method="HEAD")
    +        self.assertEqual(response.body, b"")
    +        response = self.fetch(
    +            "/all_methods", method="OTHER", allow_nonstandard_methods=True
    +        )
    +        self.assertEqual(response.body, b"OTHER")
    +
    +    def test_body_sanity_checks(self):
    +        # These methods require a body.
    +        for method in ("POST", "PUT", "PATCH"):
    +            with self.assertRaises(ValueError) as context:
    +                self.fetch("/all_methods", method=method, raise_error=True)
    +            self.assertIn("must not be None", str(context.exception))
    +
    +            resp = self.fetch(
    +                "/all_methods", method=method, allow_nonstandard_methods=True
    +            )
    +            self.assertEqual(resp.code, 200)
    +
    +        # These methods don't allow a body.
    +        for method in ("GET", "DELETE", "OPTIONS"):
    +            with self.assertRaises(ValueError) as context:
    +                self.fetch(
    +                    "/all_methods", method=method, body=b"asdf", raise_error=True
    +                )
    +            self.assertIn("must be None", str(context.exception))
    +
    +            # In most cases this can be overridden, but curl_httpclient
    +            # does not allow body with a GET at all.
    +            if method != "GET":
    +                self.fetch(
    +                    "/all_methods",
    +                    method=method,
    +                    body=b"asdf",
    +                    allow_nonstandard_methods=True,
    +                    raise_error=True,
    +                )
    +                self.assertEqual(resp.code, 200)
    +
    +    # This test causes odd failures with the combination of
    +    # curl_httpclient (at least with the version of libcurl available
    +    # on ubuntu 12.04), TwistedIOLoop, and epoll.  For POST (but not PUT),
    +    # curl decides the response came back too soon and closes the connection
    +    # to start again.  It does this *before* telling the socket callback to
    +    # unregister the FD.  Some IOLoop implementations have special kernel
    +    # integration to discover this immediately.  Tornado's IOLoops
    +    # ignore errors on remove_handler to accommodate this behavior, but
    +    # Twisted's reactor does not.  The removeReader call fails and so
    +    # do all future removeAll calls (which our tests do at cleanup).
    +    #
    +    # def test_post_307(self):
    +    #    response = self.fetch("/redirect?status=307&url=/post",
    +    #                          method="POST", body=b"arg1=foo&arg2=bar")
    +    #    self.assertEqual(response.body, b"Post arg1: foo, arg2: bar")
    +
    +    def test_put_307(self):
    +        response = self.fetch(
    +            "/redirect?status=307&url=/put", method="PUT", body=b"hello"
    +        )
    +        response.rethrow()
    +        self.assertEqual(response.body, b"Put body: hello")
    +
    +    def test_non_ascii_header(self):
    +        # Non-ascii headers are sent as latin1.
    +        response = self.fetch("/set_header?k=foo&v=%E9")
    +        response.rethrow()
    +        self.assertEqual(response.headers["Foo"], native_str(u"\u00e9"))
    +
    +    def test_response_times(self):
    +        # A few simple sanity checks of the response time fields to
    +        # make sure they're using the right basis (between the
    +        # wall-time and monotonic clocks).
    +        start_time = time.time()
    +        response = self.fetch("/hello")
    +        response.rethrow()
    +        self.assertGreaterEqual(response.request_time, 0)
    +        self.assertLess(response.request_time, 1.0)
    +        # A very crude check to make sure that start_time is based on
    +        # wall time and not the monotonic clock.
    +        self.assertLess(abs(response.start_time - start_time), 1.0)
    +
    +        for k, v in response.time_info.items():
    +            self.assertTrue(0 <= v < 1.0, "time_info[%s] out of bounds: %s" % (k, v))
    +
    +    @gen_test
    +    def test_error_after_cancel(self):
    +        fut = self.http_client.fetch(self.get_url("/404"))
    +        self.assertTrue(fut.cancel())
    +        with ExpectLog(app_log, "Exception after Future was cancelled") as el:
    +            # We can't wait on the cancelled Future any more, so just
    +            # let the IOLoop run until the exception gets logged (or
    +            # not, in which case we exit the loop and ExpectLog will
    +            # raise).
    +            for i in range(100):
    +                yield gen.sleep(0.01)
    +                if el.logged_stack:
    +                    break
    +
    +
    +class RequestProxyTest(unittest.TestCase):
    +    def test_request_set(self):
    +        proxy = _RequestProxy(
    +            HTTPRequest("http://example.com/", user_agent="foo"), dict()
    +        )
    +        self.assertEqual(proxy.user_agent, "foo")
    +
    +    def test_default_set(self):
    +        proxy = _RequestProxy(
    +            HTTPRequest("http://example.com/"), dict(network_interface="foo")
    +        )
    +        self.assertEqual(proxy.network_interface, "foo")
    +
    +    def test_both_set(self):
    +        proxy = _RequestProxy(
    +            HTTPRequest("http://example.com/", proxy_host="foo"), dict(proxy_host="bar")
    +        )
    +        self.assertEqual(proxy.proxy_host, "foo")
    +
    +    def test_neither_set(self):
    +        proxy = _RequestProxy(HTTPRequest("http://example.com/"), dict())
    +        self.assertIs(proxy.auth_username, None)
    +
    +    def test_bad_attribute(self):
    +        proxy = _RequestProxy(HTTPRequest("http://example.com/"), dict())
    +        with self.assertRaises(AttributeError):
    +            proxy.foo
    +
    +    def test_defaults_none(self):
    +        proxy = _RequestProxy(HTTPRequest("http://example.com/"), None)
    +        self.assertIs(proxy.auth_username, None)
    +
    +
    +class HTTPResponseTestCase(unittest.TestCase):
    +    def test_str(self):
    +        response = HTTPResponse(  # type: ignore
    +            HTTPRequest("http://example.com"), 200, headers={}, buffer=BytesIO()
    +        )
    +        s = str(response)
    +        self.assertTrue(s.startswith("HTTPResponse("))
    +        self.assertIn("code=200", s)
    +
    +
    +class SyncHTTPClientTest(unittest.TestCase):
    +    def setUp(self):
    +        self.server_ioloop = IOLoop()
    +        event = threading.Event()
    +
    +        @gen.coroutine
    +        def init_server():
    +            sock, self.port = bind_unused_port()
    +            app = Application([("/", HelloWorldHandler)])
    +            self.server = HTTPServer(app)
    +            self.server.add_socket(sock)
    +            event.set()
    +
    +        def start():
    +            self.server_ioloop.run_sync(init_server)
    +            self.server_ioloop.start()
    +
    +        self.server_thread = threading.Thread(target=start)
    +        self.server_thread.start()
    +        event.wait()
    +
    +        self.http_client = HTTPClient()
    +
    +    def tearDown(self):
    +        def stop_server():
    +            self.server.stop()
    +            # Delay the shutdown of the IOLoop by several iterations because
    +            # the server may still have some cleanup work left when
    +            # the client finishes with the response (this is noticeable
    +            # with http/2, which leaves a Future with an unexamined
    +            # StreamClosedError on the loop).
    +
    +            @gen.coroutine
    +            def slow_stop():
    +                yield self.server.close_all_connections()
    +                # The number of iterations is difficult to predict. Typically,
    +                # one is sufficient, although sometimes it needs more.
    +                for i in range(5):
    +                    yield
    +                self.server_ioloop.stop()
    +
    +            self.server_ioloop.add_callback(slow_stop)
    +
    +        self.server_ioloop.add_callback(stop_server)
    +        self.server_thread.join()
    +        self.http_client.close()
    +        self.server_ioloop.close(all_fds=True)
    +
    +    def get_url(self, path):
    +        return "http://127.0.0.1:%d%s" % (self.port, path)
    +
    +    def test_sync_client(self):
    +        response = self.http_client.fetch(self.get_url("/"))
    +        self.assertEqual(b"Hello world!", response.body)
    +
    +    def test_sync_client_error(self):
    +        # Synchronous HTTPClient raises errors directly; no need for
    +        # response.rethrow()
    +        with self.assertRaises(HTTPError) as assertion:
    +            self.http_client.fetch(self.get_url("/notfound"))
    +        self.assertEqual(assertion.exception.code, 404)
    +
    +
    +class SyncHTTPClientSubprocessTest(unittest.TestCase):
    +    def test_destructor_log(self):
    +        # Regression test for
    +        # https://github.com/tornadoweb/tornado/issues/2539
    +        #
    +        # In the past, the following program would log an
    +        # "inconsistent AsyncHTTPClient cache" error from a destructor
    +        # when the process is shutting down. The shutdown process is
    +        # subtle and I don't fully understand it; the failure does not
    +        # manifest if that lambda isn't there or is a simpler object
    +        # like an int (nor does it manifest in the tornado test suite
    +        # as a whole, which is why we use this subprocess).
    +        proc = subprocess.run(
    +            [
    +                sys.executable,
    +                "-c",
    +                "from tornado.httpclient import HTTPClient; f = lambda: None; c = HTTPClient()",
    +            ],
    +            stdout=subprocess.PIPE,
    +            stderr=subprocess.STDOUT,
    +            check=True,
    +        )
    +        if proc.stdout:
    +            print("STDOUT:")
    +            print(to_unicode(proc.stdout))
    +        if proc.stdout:
    +            self.fail("subprocess produced unexpected output")
    +
    +
    +class HTTPRequestTestCase(unittest.TestCase):
    +    def test_headers(self):
    +        request = HTTPRequest("http://example.com", headers={"foo": "bar"})
    +        self.assertEqual(request.headers, {"foo": "bar"})
    +
    +    def test_headers_setter(self):
    +        request = HTTPRequest("http://example.com")
    +        request.headers = {"bar": "baz"}  # type: ignore
    +        self.assertEqual(request.headers, {"bar": "baz"})
    +
    +    def test_null_headers_setter(self):
    +        request = HTTPRequest("http://example.com")
    +        request.headers = None  # type: ignore
    +        self.assertEqual(request.headers, {})
    +
    +    def test_body(self):
    +        request = HTTPRequest("http://example.com", body="foo")
    +        self.assertEqual(request.body, utf8("foo"))
    +
    +    def test_body_setter(self):
    +        request = HTTPRequest("http://example.com")
    +        request.body = "foo"  # type: ignore
    +        self.assertEqual(request.body, utf8("foo"))
    +
    +    def test_if_modified_since(self):
    +        http_date = datetime.datetime.utcnow()
    +        request = HTTPRequest("http://example.com", if_modified_since=http_date)
    +        self.assertEqual(
    +            request.headers, {"If-Modified-Since": format_timestamp(http_date)}
    +        )
    +
    +
    +class HTTPErrorTestCase(unittest.TestCase):
    +    def test_copy(self):
    +        e = HTTPError(403)
    +        e2 = copy.copy(e)
    +        self.assertIsNot(e, e2)
    +        self.assertEqual(e.code, e2.code)
    +
    +    def test_plain_error(self):
    +        e = HTTPError(403)
    +        self.assertEqual(str(e), "HTTP 403: Forbidden")
    +        self.assertEqual(repr(e), "HTTP 403: Forbidden")
    +
    +    def test_error_with_response(self):
    +        resp = HTTPResponse(HTTPRequest("http://example.com/"), 403)
    +        with self.assertRaises(HTTPError) as cm:
    +            resp.rethrow()
    +        e = cm.exception
    +        self.assertEqual(str(e), "HTTP 403: Forbidden")
    +        self.assertEqual(repr(e), "HTTP 403: Forbidden")
    diff --git a/venv/lib/python3.7/site-packages/tornado/test/httpserver_test.py b/venv/lib/python3.7/site-packages/tornado/test/httpserver_test.py
    new file mode 100644
    index 0000000..2a8b6a5
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/test/httpserver_test.py
    @@ -0,0 +1,1296 @@
    +from tornado import gen, netutil
    +from tornado.escape import (
    +    json_decode,
    +    json_encode,
    +    utf8,
    +    _unicode,
    +    recursive_unicode,
    +    native_str,
    +)
    +from tornado.http1connection import HTTP1Connection
    +from tornado.httpclient import HTTPError
    +from tornado.httpserver import HTTPServer
    +from tornado.httputil import (
    +    HTTPHeaders,
    +    HTTPMessageDelegate,
    +    HTTPServerConnectionDelegate,
    +    ResponseStartLine,
    +)
    +from tornado.iostream import IOStream
    +from tornado.locks import Event
    +from tornado.log import gen_log
    +from tornado.netutil import ssl_options_to_context
    +from tornado.simple_httpclient import SimpleAsyncHTTPClient
    +from tornado.testing import (
    +    AsyncHTTPTestCase,
    +    AsyncHTTPSTestCase,
    +    AsyncTestCase,
    +    ExpectLog,
    +    gen_test,
    +)
    +from tornado.test.util import skipOnTravis
    +from tornado.web import Application, RequestHandler, stream_request_body
    +
    +from contextlib import closing
    +import datetime
    +import gzip
    +import os
    +import shutil
    +import socket
    +import ssl
    +import sys
    +import tempfile
    +import unittest
    +from io import BytesIO
    +
    +import typing
    +
    +if typing.TYPE_CHECKING:
    +    from typing import Dict, List  # noqa: F401
    +
    +
    +async def read_stream_body(stream):
    +    """Reads an HTTP response from `stream` and returns a tuple of its
    +    start_line, headers and body."""
    +    chunks = []
    +
    +    class Delegate(HTTPMessageDelegate):
    +        def headers_received(self, start_line, headers):
    +            self.headers = headers
    +            self.start_line = start_line
    +
    +        def data_received(self, chunk):
    +            chunks.append(chunk)
    +
    +        def finish(self):
    +            conn.detach()  # type: ignore
    +
    +    conn = HTTP1Connection(stream, True)
    +    delegate = Delegate()
    +    await conn.read_response(delegate)
    +    return delegate.start_line, delegate.headers, b"".join(chunks)
    +
    +
    +class HandlerBaseTestCase(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application([("/", self.__class__.Handler)])
    +
    +    def fetch_json(self, *args, **kwargs):
    +        response = self.fetch(*args, **kwargs)
    +        response.rethrow()
    +        return json_decode(response.body)
    +
    +
    +class HelloWorldRequestHandler(RequestHandler):
    +    def initialize(self, protocol="http"):
    +        self.expected_protocol = protocol
    +
    +    def get(self):
    +        if self.request.protocol != self.expected_protocol:
    +            raise Exception("unexpected protocol")
    +        self.finish("Hello world")
    +
    +    def post(self):
    +        self.finish("Got %d bytes in POST" % len(self.request.body))
    +
    +
    +# In pre-1.0 versions of openssl, SSLv23 clients always send SSLv2
    +# ClientHello messages, which are rejected by SSLv3 and TLSv1
    +# servers.  Note that while the OPENSSL_VERSION_INFO was formally
    +# introduced in python3.2, it was present but undocumented in
    +# python 2.7
    +skipIfOldSSL = unittest.skipIf(
    +    getattr(ssl, "OPENSSL_VERSION_INFO", (0, 0)) < (1, 0),
    +    "old version of ssl module and/or openssl",
    +)
    +
    +
    +class BaseSSLTest(AsyncHTTPSTestCase):
    +    def get_app(self):
    +        return Application([("/", HelloWorldRequestHandler, dict(protocol="https"))])
    +
    +
    +class SSLTestMixin(object):
    +    def get_ssl_options(self):
    +        return dict(
    +            ssl_version=self.get_ssl_version(),
    +            **AsyncHTTPSTestCase.default_ssl_options()
    +        )
    +
    +    def get_ssl_version(self):
    +        raise NotImplementedError()
    +
    +    def test_ssl(self):
    +        response = self.fetch("/")
    +        self.assertEqual(response.body, b"Hello world")
    +
    +    def test_large_post(self):
    +        response = self.fetch("/", method="POST", body="A" * 5000)
    +        self.assertEqual(response.body, b"Got 5000 bytes in POST")
    +
    +    def test_non_ssl_request(self):
    +        # Make sure the server closes the connection when it gets a non-ssl
    +        # connection, rather than waiting for a timeout or otherwise
    +        # misbehaving.
    +        with ExpectLog(gen_log, "(SSL Error|uncaught exception)"):
    +            with ExpectLog(gen_log, "Uncaught exception", required=False):
    +                with self.assertRaises((IOError, HTTPError)):
    +                    self.fetch(
    +                        self.get_url("/").replace("https:", "http:"),
    +                        request_timeout=3600,
    +                        connect_timeout=3600,
    +                        raise_error=True,
    +                    )
    +
    +    def test_error_logging(self):
    +        # No stack traces are logged for SSL errors.
    +        with ExpectLog(gen_log, "SSL Error") as expect_log:
    +            with self.assertRaises((IOError, HTTPError)):
    +                self.fetch(
    +                    self.get_url("/").replace("https:", "http:"), raise_error=True
    +                )
    +        self.assertFalse(expect_log.logged_stack)
    +
    +
    +# Python's SSL implementation differs significantly between versions.
    +# For example, SSLv3 and TLSv1 throw an exception if you try to read
    +# from the socket before the handshake is complete, but the default
    +# of SSLv23 allows it.
    +
    +
    +class SSLv23Test(BaseSSLTest, SSLTestMixin):
    +    def get_ssl_version(self):
    +        return ssl.PROTOCOL_SSLv23
    +
    +
    +@skipIfOldSSL
    +class SSLv3Test(BaseSSLTest, SSLTestMixin):
    +    def get_ssl_version(self):
    +        return ssl.PROTOCOL_SSLv3
    +
    +
    +@skipIfOldSSL
    +class TLSv1Test(BaseSSLTest, SSLTestMixin):
    +    def get_ssl_version(self):
    +        return ssl.PROTOCOL_TLSv1
    +
    +
    +class SSLContextTest(BaseSSLTest, SSLTestMixin):
    +    def get_ssl_options(self):
    +        context = ssl_options_to_context(AsyncHTTPSTestCase.get_ssl_options(self))
    +        assert isinstance(context, ssl.SSLContext)
    +        return context
    +
    +
    +class BadSSLOptionsTest(unittest.TestCase):
    +    def test_missing_arguments(self):
    +        application = Application()
    +        self.assertRaises(
    +            KeyError,
    +            HTTPServer,
    +            application,
    +            ssl_options={"keyfile": "/__missing__.crt"},
    +        )
    +
    +    def test_missing_key(self):
    +        """A missing SSL key should cause an immediate exception."""
    +
    +        application = Application()
    +        module_dir = os.path.dirname(__file__)
    +        existing_certificate = os.path.join(module_dir, "test.crt")
    +        existing_key = os.path.join(module_dir, "test.key")
    +
    +        self.assertRaises(
    +            (ValueError, IOError),
    +            HTTPServer,
    +            application,
    +            ssl_options={"certfile": "/__mising__.crt"},
    +        )
    +        self.assertRaises(
    +            (ValueError, IOError),
    +            HTTPServer,
    +            application,
    +            ssl_options={
    +                "certfile": existing_certificate,
    +                "keyfile": "/__missing__.key",
    +            },
    +        )
    +
    +        # This actually works because both files exist
    +        HTTPServer(
    +            application,
    +            ssl_options={"certfile": existing_certificate, "keyfile": existing_key},
    +        )
    +
    +
    +class MultipartTestHandler(RequestHandler):
    +    def post(self):
    +        self.finish(
    +            {
    +                "header": self.request.headers["X-Header-Encoding-Test"],
    +                "argument": self.get_argument("argument"),
    +                "filename": self.request.files["files"][0].filename,
    +                "filebody": _unicode(self.request.files["files"][0]["body"]),
    +            }
    +        )
    +
    +
    +# This test is also called from wsgi_test
    +class HTTPConnectionTest(AsyncHTTPTestCase):
    +    def get_handlers(self):
    +        return [
    +            ("/multipart", MultipartTestHandler),
    +            ("/hello", HelloWorldRequestHandler),
    +        ]
    +
    +    def get_app(self):
    +        return Application(self.get_handlers())
    +
    +    def raw_fetch(self, headers, body, newline=b"\r\n"):
    +        with closing(IOStream(socket.socket())) as stream:
    +            self.io_loop.run_sync(
    +                lambda: stream.connect(("127.0.0.1", self.get_http_port()))
    +            )
    +            stream.write(
    +                newline.join(headers + [utf8("Content-Length: %d" % len(body))])
    +                + newline
    +                + newline
    +                + body
    +            )
    +            start_line, headers, body = self.io_loop.run_sync(
    +                lambda: read_stream_body(stream)
    +            )
    +            return body
    +
    +    def test_multipart_form(self):
    +        # Encodings here are tricky:  Headers are latin1, bodies can be
    +        # anything (we use utf8 by default).
    +        response = self.raw_fetch(
    +            [
    +                b"POST /multipart HTTP/1.0",
    +                b"Content-Type: multipart/form-data; boundary=1234567890",
    +                b"X-Header-encoding-test: \xe9",
    +            ],
    +            b"\r\n".join(
    +                [
    +                    b"Content-Disposition: form-data; name=argument",
    +                    b"",
    +                    u"\u00e1".encode("utf-8"),
    +                    b"--1234567890",
    +                    u'Content-Disposition: form-data; name="files"; filename="\u00f3"'.encode(
    +                        "utf8"
    +                    ),
    +                    b"",
    +                    u"\u00fa".encode("utf-8"),
    +                    b"--1234567890--",
    +                    b"",
    +                ]
    +            ),
    +        )
    +        data = json_decode(response)
    +        self.assertEqual(u"\u00e9", data["header"])
    +        self.assertEqual(u"\u00e1", data["argument"])
    +        self.assertEqual(u"\u00f3", data["filename"])
    +        self.assertEqual(u"\u00fa", data["filebody"])
    +
    +    def test_newlines(self):
    +        # We support both CRLF and bare LF as line separators.
    +        for newline in (b"\r\n", b"\n"):
    +            response = self.raw_fetch([b"GET /hello HTTP/1.0"], b"", newline=newline)
    +            self.assertEqual(response, b"Hello world")
    +
    +    @gen_test
    +    def test_100_continue(self):
    +        # Run through a 100-continue interaction by hand:
    +        # When given Expect: 100-continue, we get a 100 response after the
    +        # headers, and then the real response after the body.
    +        stream = IOStream(socket.socket())
    +        yield stream.connect(("127.0.0.1", self.get_http_port()))
    +        yield stream.write(
    +            b"\r\n".join(
    +                [
    +                    b"POST /hello HTTP/1.1",
    +                    b"Content-Length: 1024",
    +                    b"Expect: 100-continue",
    +                    b"Connection: close",
    +                    b"\r\n",
    +                ]
    +            )
    +        )
    +        data = yield stream.read_until(b"\r\n\r\n")
    +        self.assertTrue(data.startswith(b"HTTP/1.1 100 "), data)
    +        stream.write(b"a" * 1024)
    +        first_line = yield stream.read_until(b"\r\n")
    +        self.assertTrue(first_line.startswith(b"HTTP/1.1 200"), first_line)
    +        header_data = yield stream.read_until(b"\r\n\r\n")
    +        headers = HTTPHeaders.parse(native_str(header_data.decode("latin1")))
    +        body = yield stream.read_bytes(int(headers["Content-Length"]))
    +        self.assertEqual(body, b"Got 1024 bytes in POST")
    +        stream.close()
    +
    +
    +class EchoHandler(RequestHandler):
    +    def get(self):
    +        self.write(recursive_unicode(self.request.arguments))
    +
    +    def post(self):
    +        self.write(recursive_unicode(self.request.arguments))
    +
    +
    +class TypeCheckHandler(RequestHandler):
    +    def prepare(self):
    +        self.errors = {}  # type: Dict[str, str]
    +        fields = [
    +            ("method", str),
    +            ("uri", str),
    +            ("version", str),
    +            ("remote_ip", str),
    +            ("protocol", str),
    +            ("host", str),
    +            ("path", str),
    +            ("query", str),
    +        ]
    +        for field, expected_type in fields:
    +            self.check_type(field, getattr(self.request, field), expected_type)
    +
    +        self.check_type("header_key", list(self.request.headers.keys())[0], str)
    +        self.check_type("header_value", list(self.request.headers.values())[0], str)
    +
    +        self.check_type("cookie_key", list(self.request.cookies.keys())[0], str)
    +        self.check_type(
    +            "cookie_value", list(self.request.cookies.values())[0].value, str
    +        )
    +        # secure cookies
    +
    +        self.check_type("arg_key", list(self.request.arguments.keys())[0], str)
    +        self.check_type("arg_value", list(self.request.arguments.values())[0][0], bytes)
    +
    +    def post(self):
    +        self.check_type("body", self.request.body, bytes)
    +        self.write(self.errors)
    +
    +    def get(self):
    +        self.write(self.errors)
    +
    +    def check_type(self, name, obj, expected_type):
    +        actual_type = type(obj)
    +        if expected_type != actual_type:
    +            self.errors[name] = "expected %s, got %s" % (expected_type, actual_type)
    +
    +
    +class HTTPServerTest(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application(
    +            [
    +                ("/echo", EchoHandler),
    +                ("/typecheck", TypeCheckHandler),
    +                ("//doubleslash", EchoHandler),
    +            ]
    +        )
    +
    +    def test_query_string_encoding(self):
    +        response = self.fetch("/echo?foo=%C3%A9")
    +        data = json_decode(response.body)
    +        self.assertEqual(data, {u"foo": [u"\u00e9"]})
    +
    +    def test_empty_query_string(self):
    +        response = self.fetch("/echo?foo=&foo=")
    +        data = json_decode(response.body)
    +        self.assertEqual(data, {u"foo": [u"", u""]})
    +
    +    def test_empty_post_parameters(self):
    +        response = self.fetch("/echo", method="POST", body="foo=&bar=")
    +        data = json_decode(response.body)
    +        self.assertEqual(data, {u"foo": [u""], u"bar": [u""]})
    +
    +    def test_types(self):
    +        headers = {"Cookie": "foo=bar"}
    +        response = self.fetch("/typecheck?foo=bar", headers=headers)
    +        data = json_decode(response.body)
    +        self.assertEqual(data, {})
    +
    +        response = self.fetch(
    +            "/typecheck", method="POST", body="foo=bar", headers=headers
    +        )
    +        data = json_decode(response.body)
    +        self.assertEqual(data, {})
    +
    +    def test_double_slash(self):
    +        # urlparse.urlsplit (which tornado.httpserver used to use
    +        # incorrectly) would parse paths beginning with "//" as
    +        # protocol-relative urls.
    +        response = self.fetch("//doubleslash")
    +        self.assertEqual(200, response.code)
    +        self.assertEqual(json_decode(response.body), {})
    +
    +    def test_malformed_body(self):
    +        # parse_qs is pretty forgiving, but it will fail on python 3
    +        # if the data is not utf8.
    +        with ExpectLog(gen_log, "Invalid x-www-form-urlencoded body"):
    +            response = self.fetch(
    +                "/echo",
    +                method="POST",
    +                headers={"Content-Type": "application/x-www-form-urlencoded"},
    +                body=b"\xe9",
    +            )
    +        self.assertEqual(200, response.code)
    +        self.assertEqual(b"{}", response.body)
    +
    +
    +class HTTPServerRawTest(AsyncHTTPTestCase):
    +    def get_app(self):
    +        return Application([("/echo", EchoHandler)])
    +
    +    def setUp(self):
    +        super(HTTPServerRawTest, self).setUp()
    +        self.stream = IOStream(socket.socket())
    +        self.io_loop.run_sync(
    +            lambda: self.stream.connect(("127.0.0.1", self.get_http_port()))
    +        )
    +
    +    def tearDown(self):
    +        self.stream.close()
    +        super(HTTPServerRawTest, self).tearDown()
    +
    +    def test_empty_request(self):
    +        self.stream.close()
    +        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)
    +        self.wait()
    +
    +    def test_malformed_first_line_response(self):
    +        with ExpectLog(gen_log, ".*Malformed HTTP request line"):
    +            self.stream.write(b"asdf\r\n\r\n")
    +            start_line, headers, response = self.io_loop.run_sync(
    +                lambda: read_stream_body(self.stream)
    +            )
    +            self.assertEqual("HTTP/1.1", start_line.version)
    +            self.assertEqual(400, start_line.code)
    +            self.assertEqual("Bad Request", start_line.reason)
    +
    +    def test_malformed_first_line_log(self):
    +        with ExpectLog(gen_log, ".*Malformed HTTP request line"):
    +            self.stream.write(b"asdf\r\n\r\n")
    +            # TODO: need an async version of ExpectLog so we don't need
    +            # hard-coded timeouts here.
    +            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)
    +            self.wait()
    +
    +    def test_malformed_headers(self):
    +        with ExpectLog(gen_log, ".*Malformed HTTP message.*no colon in header line"):
    +            self.stream.write(b"GET / HTTP/1.0\r\nasdf\r\n\r\n")
    +            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)
    +            self.wait()
    +
    +    def test_chunked_request_body(self):
    +        # Chunked requests are not widely supported and we don't have a way
    +        # to generate them in AsyncHTTPClient, but HTTPServer will read them.
    +        self.stream.write(
    +            b"""\
    +POST /echo HTTP/1.1
    +Transfer-Encoding: chunked
    +Content-Type: application/x-www-form-urlencoded
    +
    +4
    +foo=
    +3
    +bar
    +0
    +
    +""".replace(
    +                b"\n", b"\r\n"
    +            )
    +        )
    +        start_line, headers, response = self.io_loop.run_sync(
    +            lambda: read_stream_body(self.stream)
    +        )
    +        self.assertEqual(json_decode(response), {u"foo": [u"bar"]})
    +
    +    def test_chunked_request_uppercase(self):
    +        # As per RFC 2616 section 3.6, "Transfer-Encoding" header's value is
    +        # case-insensitive.
    +        self.stream.write(
    +            b"""\
    +POST /echo HTTP/1.1
    +Transfer-Encoding: Chunked
    +Content-Type: application/x-www-form-urlencoded
    +
    +4
    +foo=
    +3
    +bar
    +0
    +
    +""".replace(
    +                b"\n", b"\r\n"
    +            )
    +        )
    +        start_line, headers, response = self.io_loop.run_sync(
    +            lambda: read_stream_body(self.stream)
    +        )
    +        self.assertEqual(json_decode(response), {u"foo": [u"bar"]})
    +
    +    @gen_test
    +    def test_invalid_content_length(self):
    +        with ExpectLog(gen_log, ".*Only integer Content-Length is allowed"):
    +            self.stream.write(
    +                b"""\
    +POST /echo HTTP/1.1
    +Content-Length: foo
    +
    +bar
    +
    +""".replace(
    +                    b"\n", b"\r\n"
    +                )
    +            )
    +            yield self.stream.read_until_close()
    +
    +
    +class XHeaderTest(HandlerBaseTestCase):
    +    class Handler(RequestHandler):
    +        def get(self):
    +            self.set_header("request-version", self.request.version)
    +            self.write(
    +                dict(
    +                    remote_ip=self.request.remote_ip,
    +                    remote_protocol=self.request.protocol,
    +                )
    +            )
    +
    +    def get_httpserver_options(self):
    +        return dict(xheaders=True, trusted_downstream=["5.5.5.5"])
    +
    +    def test_ip_headers(self):
    +        self.assertEqual(self.fetch_json("/")["remote_ip"], "127.0.0.1")
    +
    +        valid_ipv4 = {"X-Real-IP": "4.4.4.4"}
    +        self.assertEqual(
    +            self.fetch_json("/", headers=valid_ipv4)["remote_ip"], "4.4.4.4"
    +        )
    +
    +        valid_ipv4_list = {"X-Forwarded-For": "127.0.0.1, 4.4.4.4"}
    +        self.assertEqual(
    +            self.fetch_json("/", headers=valid_ipv4_list)["remote_ip"], "4.4.4.4"
    +        )
    +
    +        valid_ipv6 = {"X-Real-IP": "2620:0:1cfe:face:b00c::3"}
    +        self.assertEqual(
    +            self.fetch_json("/", headers=valid_ipv6)["remote_ip"],
    +            "2620:0:1cfe:face:b00c::3",
    +        )
    +
    +        valid_ipv6_list = {"X-Forwarded-For": "::1, 2620:0:1cfe:face:b00c::3"}
    +        self.assertEqual(
    +            self.fetch_json("/", headers=valid_ipv6_list)["remote_ip"],
    +            "2620:0:1cfe:face:b00c::3",
    +        )
    +
    +        invalid_chars = {"X-Real-IP": "4.4.4.4
    +
    +'
    +            for p in paths
    +        )
    +
    +    def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:
    +        """Default method used to render the final embedded js for the
    +        rendered webpage.
    +
    +        Override this method in a sub-classed controller to change the output.
    +        """
    +        return (
    +            b'"
    +        )
    +
    +    def render_linked_css(self, css_files: Iterable[str]) -> str:
    +        """Default method used to render the final css links for the
    +        rendered webpage.
    +
    +        Override this method in a sub-classed controller to change the output.
    +        """
    +        paths = []
    +        unique_paths = set()  # type: Set[str]
    +
    +        for path in css_files:
    +            if not is_absolute(path):
    +                path = self.static_url(path)
    +            if path not in unique_paths:
    +                paths.append(path)
    +                unique_paths.add(path)
    +
    +        return "".join(
    +            ''
    +            for p in paths
    +        )
    +
    +    def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:
    +        """Default method used to render the final embedded css for the
    +        rendered webpage.
    +
    +        Override this method in a sub-classed controller to change the output.
    +        """
    +        return b'"
    +
    +    def render_string(self, template_name: str, **kwargs: Any) -> bytes:
    +        """Generate the given template with the given arguments.
    +
    +        We return the generated byte string (in utf8). To generate and
    +        write a template as a response, use render() above.
    +        """
    +        # If no template_path is specified, use the path of the calling file
    +        template_path = self.get_template_path()
    +        if not template_path:
    +            frame = sys._getframe(0)
    +            web_file = frame.f_code.co_filename
    +            while frame.f_code.co_filename == web_file:
    +                frame = frame.f_back
    +            assert frame.f_code.co_filename is not None
    +            template_path = os.path.dirname(frame.f_code.co_filename)
    +        with RequestHandler._template_loader_lock:
    +            if template_path not in RequestHandler._template_loaders:
    +                loader = self.create_template_loader(template_path)
    +                RequestHandler._template_loaders[template_path] = loader
    +            else:
    +                loader = RequestHandler._template_loaders[template_path]
    +        t = loader.load(template_name)
    +        namespace = self.get_template_namespace()
    +        namespace.update(kwargs)
    +        return t.generate(**namespace)
    +
    +    def get_template_namespace(self) -> Dict[str, Any]:
    +        """Returns a dictionary to be used as the default template namespace.
    +
    +        May be overridden by subclasses to add or modify values.
    +
    +        The results of this method will be combined with additional
    +        defaults in the `tornado.template` module and keyword arguments
    +        to `render` or `render_string`.
    +        """
    +        namespace = dict(
    +            handler=self,
    +            request=self.request,
    +            current_user=self.current_user,
    +            locale=self.locale,
    +            _=self.locale.translate,
    +            pgettext=self.locale.pgettext,
    +            static_url=self.static_url,
    +            xsrf_form_html=self.xsrf_form_html,
    +            reverse_url=self.reverse_url,
    +        )
    +        namespace.update(self.ui)
    +        return namespace
    +
    +    def create_template_loader(self, template_path: str) -> template.BaseLoader:
    +        """Returns a new template loader for the given path.
    +
    +        May be overridden by subclasses.  By default returns a
    +        directory-based loader on the given path, using the
    +        ``autoescape`` and ``template_whitespace`` application
    +        settings.  If a ``template_loader`` application setting is
    +        supplied, uses that instead.
    +        """
    +        settings = self.application.settings
    +        if "template_loader" in settings:
    +            return settings["template_loader"]
    +        kwargs = {}
    +        if "autoescape" in settings:
    +            # autoescape=None means "no escaping", so we have to be sure
    +            # to only pass this kwarg if the user asked for it.
    +            kwargs["autoescape"] = settings["autoescape"]
    +        if "template_whitespace" in settings:
    +            kwargs["whitespace"] = settings["template_whitespace"]
    +        return template.Loader(template_path, **kwargs)
    +
    +    def flush(self, include_footers: bool = False) -> "Future[None]":
    +        """Flushes the current output buffer to the network.
    +
    +        The ``callback`` argument, if given, can be used for flow control:
    +        it will be run when all flushed data has been written to the socket.
    +        Note that only one flush callback can be outstanding at a time;
    +        if another flush occurs before the previous flush's callback
    +        has been run, the previous callback will be discarded.
    +
    +        .. versionchanged:: 4.0
    +           Now returns a `.Future` if no callback is given.
    +
    +        .. versionchanged:: 6.0
    +
    +           The ``callback`` argument was removed.
    +        """
    +        assert self.request.connection is not None
    +        chunk = b"".join(self._write_buffer)
    +        self._write_buffer = []
    +        if not self._headers_written:
    +            self._headers_written = True
    +            for transform in self._transforms:
    +                assert chunk is not None
    +                self._status_code, self._headers, chunk = transform.transform_first_chunk(
    +                    self._status_code, self._headers, chunk, include_footers
    +                )
    +            # Ignore the chunk and only write the headers for HEAD requests
    +            if self.request.method == "HEAD":
    +                chunk = b""
    +
    +            # Finalize the cookie headers (which have been stored in a side
    +            # object so an outgoing cookie could be overwritten before it
    +            # is sent).
    +            if hasattr(self, "_new_cookie"):
    +                for cookie in self._new_cookie.values():
    +                    self.add_header("Set-Cookie", cookie.OutputString(None))
    +
    +            start_line = httputil.ResponseStartLine("", self._status_code, self._reason)
    +            return self.request.connection.write_headers(
    +                start_line, self._headers, chunk
    +            )
    +        else:
    +            for transform in self._transforms:
    +                chunk = transform.transform_chunk(chunk, include_footers)
    +            # Ignore the chunk and only write the headers for HEAD requests
    +            if self.request.method != "HEAD":
    +                return self.request.connection.write(chunk)
    +            else:
    +                future = Future()  # type: Future[None]
    +                future.set_result(None)
    +                return future
    +
    +    def finish(self, chunk: Union[str, bytes, dict] = None) -> "Future[None]":
    +        """Finishes this response, ending the HTTP request.
    +
    +        Passing a ``chunk`` to ``finish()`` is equivalent to passing that
    +        chunk to ``write()`` and then calling ``finish()`` with no arguments.
    +
    +        Returns a `.Future` which may optionally be awaited to track the sending
    +        of the response to the client. This `.Future` resolves when all the response
    +        data has been sent, and raises an error if the connection is closed before all
    +        data can be sent.
    +
    +        .. versionchanged:: 5.1
    +
    +           Now returns a `.Future` instead of ``None``.
    +        """
    +        if self._finished:
    +            raise RuntimeError("finish() called twice")
    +
    +        if chunk is not None:
    +            self.write(chunk)
    +
    +        # Automatically support ETags and add the Content-Length header if
    +        # we have not flushed any content yet.
    +        if not self._headers_written:
    +            if (
    +                self._status_code == 200
    +                and self.request.method in ("GET", "HEAD")
    +                and "Etag" not in self._headers
    +            ):
    +                self.set_etag_header()
    +                if self.check_etag_header():
    +                    self._write_buffer = []
    +                    self.set_status(304)
    +            if self._status_code in (204, 304) or (
    +                self._status_code >= 100 and self._status_code < 200
    +            ):
    +                assert not self._write_buffer, (
    +                    "Cannot send body with %s" % self._status_code
    +                )
    +                self._clear_headers_for_304()
    +            elif "Content-Length" not in self._headers:
    +                content_length = sum(len(part) for part in self._write_buffer)
    +                self.set_header("Content-Length", content_length)
    +
    +        assert self.request.connection is not None
    +        # Now that the request is finished, clear the callback we
    +        # set on the HTTPConnection (which would otherwise prevent the
    +        # garbage collection of the RequestHandler when there
    +        # are keepalive connections)
    +        self.request.connection.set_close_callback(None)  # type: ignore
    +
    +        future = self.flush(include_footers=True)
    +        self.request.connection.finish()
    +        self._log()
    +        self._finished = True
    +        self.on_finish()
    +        self._break_cycles()
    +        return future
    +
    +    def detach(self) -> iostream.IOStream:
    +        """Take control of the underlying stream.
    +
    +        Returns the underlying `.IOStream` object and stops all
    +        further HTTP processing. Intended for implementing protocols
    +        like websockets that tunnel over an HTTP handshake.
    +
    +        This method is only supported when HTTP/1.1 is used.
    +
    +        .. versionadded:: 5.1
    +        """
    +        self._finished = True
    +        # TODO: add detach to HTTPConnection?
    +        return self.request.connection.detach()  # type: ignore
    +
    +    def _break_cycles(self) -> None:
    +        # Break up a reference cycle between this handler and the
    +        # _ui_module closures to allow for faster GC on CPython.
    +        self.ui = None  # type: ignore
    +
    +    def send_error(self, status_code: int = 500, **kwargs: Any) -> None:
    +        """Sends the given HTTP error code to the browser.
    +
    +        If `flush()` has already been called, it is not possible to send
    +        an error, so this method will simply terminate the response.
    +        If output has been written but not yet flushed, it will be discarded
    +        and replaced with the error page.
    +
    +        Override `write_error()` to customize the error page that is returned.
    +        Additional keyword arguments are passed through to `write_error`.
    +        """
    +        if self._headers_written:
    +            gen_log.error("Cannot send error response after headers written")
    +            if not self._finished:
    +                # If we get an error between writing headers and finishing,
    +                # we are unlikely to be able to finish due to a
    +                # Content-Length mismatch. Try anyway to release the
    +                # socket.
    +                try:
    +                    self.finish()
    +                except Exception:
    +                    gen_log.error("Failed to flush partial response", exc_info=True)
    +            return
    +        self.clear()
    +
    +        reason = kwargs.get("reason")
    +        if "exc_info" in kwargs:
    +            exception = kwargs["exc_info"][1]
    +            if isinstance(exception, HTTPError) and exception.reason:
    +                reason = exception.reason
    +        self.set_status(status_code, reason=reason)
    +        try:
    +            self.write_error(status_code, **kwargs)
    +        except Exception:
    +            app_log.error("Uncaught exception in write_error", exc_info=True)
    +        if not self._finished:
    +            self.finish()
    +
    +    def write_error(self, status_code: int, **kwargs: Any) -> None:
    +        """Override to implement custom error pages.
    +
    +        ``write_error`` may call `write`, `render`, `set_header`, etc
    +        to produce output as usual.
    +
    +        If this error was caused by an uncaught exception (including
    +        HTTPError), an ``exc_info`` triple will be available as
    +        ``kwargs["exc_info"]``.  Note that this exception may not be
    +        the "current" exception for purposes of methods like
    +        ``sys.exc_info()`` or ``traceback.format_exc``.
    +        """
    +        if self.settings.get("serve_traceback") and "exc_info" in kwargs:
    +            # in debug mode, try to send a traceback
    +            self.set_header("Content-Type", "text/plain")
    +            for line in traceback.format_exception(*kwargs["exc_info"]):
    +                self.write(line)
    +            self.finish()
    +        else:
    +            self.finish(
    +                "%(code)d: %(message)s"
    +                "%(code)d: %(message)s"
    +                % {"code": status_code, "message": self._reason}
    +            )
    +
    +    @property
    +    def locale(self) -> tornado.locale.Locale:
    +        """The locale for the current session.
    +
    +        Determined by either `get_user_locale`, which you can override to
    +        set the locale based on, e.g., a user preference stored in a
    +        database, or `get_browser_locale`, which uses the ``Accept-Language``
    +        header.
    +
    +        .. versionchanged: 4.1
    +           Added a property setter.
    +        """
    +        if not hasattr(self, "_locale"):
    +            loc = self.get_user_locale()
    +            if loc is not None:
    +                self._locale = loc
    +            else:
    +                self._locale = self.get_browser_locale()
    +                assert self._locale
    +        return self._locale
    +
    +    @locale.setter
    +    def locale(self, value: tornado.locale.Locale) -> None:
    +        self._locale = value
    +
    +    def get_user_locale(self) -> Optional[tornado.locale.Locale]:
    +        """Override to determine the locale from the authenticated user.
    +
    +        If None is returned, we fall back to `get_browser_locale()`.
    +
    +        This method should return a `tornado.locale.Locale` object,
    +        most likely obtained via a call like ``tornado.locale.get("en")``
    +        """
    +        return None
    +
    +    def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale:
    +        """Determines the user's locale from ``Accept-Language`` header.
    +
    +        See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    +        """
    +        if "Accept-Language" in self.request.headers:
    +            languages = self.request.headers["Accept-Language"].split(",")
    +            locales = []
    +            for language in languages:
    +                parts = language.strip().split(";")
    +                if len(parts) > 1 and parts[1].startswith("q="):
    +                    try:
    +                        score = float(parts[1][2:])
    +                    except (ValueError, TypeError):
    +                        score = 0.0
    +                else:
    +                    score = 1.0
    +                locales.append((parts[0], score))
    +            if locales:
    +                locales.sort(key=lambda pair: pair[1], reverse=True)
    +                codes = [l[0] for l in locales]
    +                return locale.get(*codes)
    +        return locale.get(default)
    +
    +    @property
    +    def current_user(self) -> Any:
    +        """The authenticated user for this request.
    +
    +        This is set in one of two ways:
    +
    +        * A subclass may override `get_current_user()`, which will be called
    +          automatically the first time ``self.current_user`` is accessed.
    +          `get_current_user()` will only be called once per request,
    +          and is cached for future access::
    +
    +              def get_current_user(self):
    +                  user_cookie = self.get_secure_cookie("user")
    +                  if user_cookie:
    +                      return json.loads(user_cookie)
    +                  return None
    +
    +        * It may be set as a normal variable, typically from an overridden
    +          `prepare()`::
    +
    +              @gen.coroutine
    +              def prepare(self):
    +                  user_id_cookie = self.get_secure_cookie("user_id")
    +                  if user_id_cookie:
    +                      self.current_user = yield load_user(user_id_cookie)
    +
    +        Note that `prepare()` may be a coroutine while `get_current_user()`
    +        may not, so the latter form is necessary if loading the user requires
    +        asynchronous operations.
    +
    +        The user object may be any type of the application's choosing.
    +        """
    +        if not hasattr(self, "_current_user"):
    +            self._current_user = self.get_current_user()
    +        return self._current_user
    +
    +    @current_user.setter
    +    def current_user(self, value: Any) -> None:
    +        self._current_user = value
    +
    +    def get_current_user(self) -> Any:
    +        """Override to determine the current user from, e.g., a cookie.
    +
    +        This method may not be a coroutine.
    +        """
    +        return None
    +
    +    def get_login_url(self) -> str:
    +        """Override to customize the login URL based on the request.
    +
    +        By default, we use the ``login_url`` application setting.
    +        """
    +        self.require_setting("login_url", "@tornado.web.authenticated")
    +        return self.application.settings["login_url"]
    +
    +    def get_template_path(self) -> Optional[str]:
    +        """Override to customize template path for each handler.
    +
    +        By default, we use the ``template_path`` application setting.
    +        Return None to load templates relative to the calling file.
    +        """
    +        return self.application.settings.get("template_path")
    +
    +    @property
    +    def xsrf_token(self) -> bytes:
    +        """The XSRF-prevention token for the current user/session.
    +
    +        To prevent cross-site request forgery, we set an '_xsrf' cookie
    +        and include the same '_xsrf' value as an argument with all POST
    +        requests. If the two do not match, we reject the form submission
    +        as a potential forgery.
    +
    +        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
    +
    +        This property is of type `bytes`, but it contains only ASCII
    +        characters. If a character string is required, there is no
    +        need to base64-encode it; just decode the byte string as
    +        UTF-8.
    +
    +        .. versionchanged:: 3.2.2
    +           The xsrf token will now be have a random mask applied in every
    +           request, which makes it safe to include the token in pages
    +           that are compressed.  See http://breachattack.com for more
    +           information on the issue fixed by this change.  Old (version 1)
    +           cookies will be converted to version 2 when this method is called
    +           unless the ``xsrf_cookie_version`` `Application` setting is
    +           set to 1.
    +
    +        .. versionchanged:: 4.3
    +           The ``xsrf_cookie_kwargs`` `Application` setting may be
    +           used to supply additional cookie options (which will be
    +           passed directly to `set_cookie`). For example,
    +           ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``
    +           will set the ``secure`` and ``httponly`` flags on the
    +           ``_xsrf`` cookie.
    +        """
    +        if not hasattr(self, "_xsrf_token"):
    +            version, token, timestamp = self._get_raw_xsrf_token()
    +            output_version = self.settings.get("xsrf_cookie_version", 2)
    +            cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {})
    +            if output_version == 1:
    +                self._xsrf_token = binascii.b2a_hex(token)
    +            elif output_version == 2:
    +                mask = os.urandom(4)
    +                self._xsrf_token = b"|".join(
    +                    [
    +                        b"2",
    +                        binascii.b2a_hex(mask),
    +                        binascii.b2a_hex(_websocket_mask(mask, token)),
    +                        utf8(str(int(timestamp))),
    +                    ]
    +                )
    +            else:
    +                raise ValueError("unknown xsrf cookie version %d", output_version)
    +            if version is None:
    +                if self.current_user and "expires_days" not in cookie_kwargs:
    +                    cookie_kwargs["expires_days"] = 30
    +                self.set_cookie("_xsrf", self._xsrf_token, **cookie_kwargs)
    +        return self._xsrf_token
    +
    +    def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
    +        """Read or generate the xsrf token in its raw form.
    +
    +        The raw_xsrf_token is a tuple containing:
    +
    +        * version: the version of the cookie from which this token was read,
    +          or None if we generated a new token in this request.
    +        * token: the raw token data; random (non-ascii) bytes.
    +        * timestamp: the time this token was generated (will not be accurate
    +          for version 1 cookies)
    +        """
    +        if not hasattr(self, "_raw_xsrf_token"):
    +            cookie = self.get_cookie("_xsrf")
    +            if cookie:
    +                version, token, timestamp = self._decode_xsrf_token(cookie)
    +            else:
    +                version, token, timestamp = None, None, None
    +            if token is None:
    +                version = None
    +                token = os.urandom(16)
    +                timestamp = time.time()
    +            assert token is not None
    +            assert timestamp is not None
    +            self._raw_xsrf_token = (version, token, timestamp)
    +        return self._raw_xsrf_token
    +
    +    def _decode_xsrf_token(
    +        self, cookie: str
    +    ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]:
    +        """Convert a cookie string into a the tuple form returned by
    +        _get_raw_xsrf_token.
    +        """
    +
    +        try:
    +            m = _signed_value_version_re.match(utf8(cookie))
    +
    +            if m:
    +                version = int(m.group(1))
    +                if version == 2:
    +                    _, mask_str, masked_token, timestamp_str = cookie.split("|")
    +
    +                    mask = binascii.a2b_hex(utf8(mask_str))
    +                    token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))
    +                    timestamp = int(timestamp_str)
    +                    return version, token, timestamp
    +                else:
    +                    # Treat unknown versions as not present instead of failing.
    +                    raise Exception("Unknown xsrf cookie version")
    +            else:
    +                version = 1
    +                try:
    +                    token = binascii.a2b_hex(utf8(cookie))
    +                except (binascii.Error, TypeError):
    +                    token = utf8(cookie)
    +                # We don't have a usable timestamp in older versions.
    +                timestamp = int(time.time())
    +                return (version, token, timestamp)
    +        except Exception:
    +            # Catch exceptions and return nothing instead of failing.
    +            gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True)
    +            return None, None, None
    +
    +    def check_xsrf_cookie(self) -> None:
    +        """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
    +
    +        To prevent cross-site request forgery, we set an ``_xsrf``
    +        cookie and include the same value as a non-cookie
    +        field with all ``POST`` requests. If the two do not match, we
    +        reject the form submission as a potential forgery.
    +
    +        The ``_xsrf`` value may be set as either a form field named ``_xsrf``
    +        or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
    +        (the latter is accepted for compatibility with Django).
    +
    +        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
    +
    +        .. versionchanged:: 3.2.2
    +           Added support for cookie version 2.  Both versions 1 and 2 are
    +           supported.
    +        """
    +        # Prior to release 1.1.1, this check was ignored if the HTTP header
    +        # ``X-Requested-With: XMLHTTPRequest`` was present.  This exception
    +        # has been shown to be insecure and has been removed.  For more
    +        # information please see
    +        # http://www.djangoproject.com/weblog/2011/feb/08/security/
    +        # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
    +        token = (
    +            self.get_argument("_xsrf", None)
    +            or self.request.headers.get("X-Xsrftoken")
    +            or self.request.headers.get("X-Csrftoken")
    +        )
    +        if not token:
    +            raise HTTPError(403, "'_xsrf' argument missing from POST")
    +        _, token, _ = self._decode_xsrf_token(token)
    +        _, expected_token, _ = self._get_raw_xsrf_token()
    +        if not token:
    +            raise HTTPError(403, "'_xsrf' argument has invalid format")
    +        if not hmac.compare_digest(utf8(token), utf8(expected_token)):
    +            raise HTTPError(403, "XSRF cookie does not match POST argument")
    +
    +    def xsrf_form_html(self) -> str:
    +        """An HTML ```` element to be included with all POST forms.
    +
    +        It defines the ``_xsrf`` input value, which we check on all POST
    +        requests to prevent cross-site request forgery. If you have set
    +        the ``xsrf_cookies`` application setting, you must include this
    +        HTML within all of your HTML forms.
    +
    +        In a template, this method should be called with ``{% module
    +        xsrf_form_html() %}``
    +
    +        See `check_xsrf_cookie()` above for more information.
    +        """
    +        return (
    +            ''
    +        )
    +
    +    def static_url(self, path: str, include_host: bool = None, **kwargs: Any) -> str:
    +        """Returns a static URL for the given relative static file path.
    +
    +        This method requires you set the ``static_path`` setting in your
    +        application (which specifies the root directory of your static
    +        files).
    +
    +        This method returns a versioned url (by default appending
    +        ``?v=``), which allows the static files to be
    +        cached indefinitely.  This can be disabled by passing
    +        ``include_version=False`` (in the default implementation;
    +        other static file implementations are not required to support
    +        this, but they may support other options).
    +
    +        By default this method returns URLs relative to the current
    +        host, but if ``include_host`` is true the URL returned will be
    +        absolute.  If this handler has an ``include_host`` attribute,
    +        that value will be used as the default for all `static_url`
    +        calls that do not pass ``include_host`` as a keyword argument.
    +
    +        """
    +        self.require_setting("static_path", "static_url")
    +        get_url = self.settings.get(
    +            "static_handler_class", StaticFileHandler
    +        ).make_static_url
    +
    +        if include_host is None:
    +            include_host = getattr(self, "include_host", False)
    +
    +        if include_host:
    +            base = self.request.protocol + "://" + self.request.host
    +        else:
    +            base = ""
    +
    +        return base + get_url(self.settings, path, **kwargs)
    +
    +    def require_setting(self, name: str, feature: str = "this feature") -> None:
    +        """Raises an exception if the given app setting is not defined."""
    +        if not self.application.settings.get(name):
    +            raise Exception(
    +                "You must define the '%s' setting in your "
    +                "application to use %s" % (name, feature)
    +            )
    +
    +    def reverse_url(self, name: str, *args: Any) -> str:
    +        """Alias for `Application.reverse_url`."""
    +        return self.application.reverse_url(name, *args)
    +
    +    def compute_etag(self) -> Optional[str]:
    +        """Computes the etag header to be used for this request.
    +
    +        By default uses a hash of the content written so far.
    +
    +        May be overridden to provide custom etag implementations,
    +        or may return None to disable tornado's default etag support.
    +        """
    +        hasher = hashlib.sha1()
    +        for part in self._write_buffer:
    +            hasher.update(part)
    +        return '"%s"' % hasher.hexdigest()
    +
    +    def set_etag_header(self) -> None:
    +        """Sets the response's Etag header using ``self.compute_etag()``.
    +
    +        Note: no header will be set if ``compute_etag()`` returns ``None``.
    +
    +        This method is called automatically when the request is finished.
    +        """
    +        etag = self.compute_etag()
    +        if etag is not None:
    +            self.set_header("Etag", etag)
    +
    +    def check_etag_header(self) -> bool:
    +        """Checks the ``Etag`` header against requests's ``If-None-Match``.
    +
    +        Returns ``True`` if the request's Etag matches and a 304 should be
    +        returned. For example::
    +
    +            self.set_etag_header()
    +            if self.check_etag_header():
    +                self.set_status(304)
    +                return
    +
    +        This method is called automatically when the request is finished,
    +        but may be called earlier for applications that override
    +        `compute_etag` and want to do an early check for ``If-None-Match``
    +        before completing the request.  The ``Etag`` header should be set
    +        (perhaps with `set_etag_header`) before calling this method.
    +        """
    +        computed_etag = utf8(self._headers.get("Etag", ""))
    +        # Find all weak and strong etag values from If-None-Match header
    +        # because RFC 7232 allows multiple etag values in a single header.
    +        etags = re.findall(
    +            br'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", ""))
    +        )
    +        if not computed_etag or not etags:
    +            return False
    +
    +        match = False
    +        if etags[0] == b"*":
    +            match = True
    +        else:
    +            # Use a weak comparison when comparing entity-tags.
    +            def val(x: bytes) -> bytes:
    +                return x[2:] if x.startswith(b"W/") else x
    +
    +            for etag in etags:
    +                if val(etag) == val(computed_etag):
    +                    match = True
    +                    break
    +        return match
    +
    +    async def _execute(
    +        self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes
    +    ) -> None:
    +        """Executes this request with the given output transforms."""
    +        self._transforms = transforms
    +        try:
    +            if self.request.method not in self.SUPPORTED_METHODS:
    +                raise HTTPError(405)
    +            self.path_args = [self.decode_argument(arg) for arg in args]
    +            self.path_kwargs = dict(
    +                (k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items()
    +            )
    +            # If XSRF cookies are turned on, reject form submissions without
    +            # the proper cookie
    +            if self.request.method not in (
    +                "GET",
    +                "HEAD",
    +                "OPTIONS",
    +            ) and self.application.settings.get("xsrf_cookies"):
    +                self.check_xsrf_cookie()
    +
    +            result = self.prepare()
    +            if result is not None:
    +                result = await result
    +            if self._prepared_future is not None:
    +                # Tell the Application we've finished with prepare()
    +                # and are ready for the body to arrive.
    +                future_set_result_unless_cancelled(self._prepared_future, None)
    +            if self._finished:
    +                return
    +
    +            if _has_stream_request_body(self.__class__):
    +                # In streaming mode request.body is a Future that signals
    +                # the body has been completely received.  The Future has no
    +                # result; the data has been passed to self.data_received
    +                # instead.
    +                try:
    +                    await self.request._body_future
    +                except iostream.StreamClosedError:
    +                    return
    +
    +            method = getattr(self, self.request.method.lower())
    +            result = method(*self.path_args, **self.path_kwargs)
    +            if result is not None:
    +                result = await result
    +            if self._auto_finish and not self._finished:
    +                self.finish()
    +        except Exception as e:
    +            try:
    +                self._handle_request_exception(e)
    +            except Exception:
    +                app_log.error("Exception in exception handler", exc_info=True)
    +            finally:
    +                # Unset result to avoid circular references
    +                result = None
    +            if self._prepared_future is not None and not self._prepared_future.done():
    +                # In case we failed before setting _prepared_future, do it
    +                # now (to unblock the HTTP server).  Note that this is not
    +                # in a finally block to avoid GC issues prior to Python 3.4.
    +                self._prepared_future.set_result(None)
    +
    +    def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
    +        """Implement this method to handle streamed request data.
    +
    +        Requires the `.stream_request_body` decorator.
    +
    +        May be a coroutine for flow control.
    +        """
    +        raise NotImplementedError()
    +
    +    def _log(self) -> None:
    +        """Logs the current request.
    +
    +        Sort of deprecated since this functionality was moved to the
    +        Application, but left in place for the benefit of existing apps
    +        that have overridden this method.
    +        """
    +        self.application.log_request(self)
    +
    +    def _request_summary(self) -> str:
    +        return "%s %s (%s)" % (
    +            self.request.method,
    +            self.request.uri,
    +            self.request.remote_ip,
    +        )
    +
    +    def _handle_request_exception(self, e: BaseException) -> None:
    +        if isinstance(e, Finish):
    +            # Not an error; just finish the request without logging.
    +            if not self._finished:
    +                self.finish(*e.args)
    +            return
    +        try:
    +            self.log_exception(*sys.exc_info())
    +        except Exception:
    +            # An error here should still get a best-effort send_error()
    +            # to avoid leaking the connection.
    +            app_log.error("Error in exception logger", exc_info=True)
    +        if self._finished:
    +            # Extra errors after the request has been finished should
    +            # be logged, but there is no reason to continue to try and
    +            # send a response.
    +            return
    +        if isinstance(e, HTTPError):
    +            self.send_error(e.status_code, exc_info=sys.exc_info())
    +        else:
    +            self.send_error(500, exc_info=sys.exc_info())
    +
    +    def log_exception(
    +        self,
    +        typ: "Optional[Type[BaseException]]",
    +        value: Optional[BaseException],
    +        tb: Optional[TracebackType],
    +    ) -> None:
    +        """Override to customize logging of uncaught exceptions.
    +
    +        By default logs instances of `HTTPError` as warnings without
    +        stack traces (on the ``tornado.general`` logger), and all
    +        other exceptions as errors with stack traces (on the
    +        ``tornado.application`` logger).
    +
    +        .. versionadded:: 3.1
    +        """
    +        if isinstance(value, HTTPError):
    +            if value.log_message:
    +                format = "%d %s: " + value.log_message
    +                args = [value.status_code, self._request_summary()] + list(value.args)
    +                gen_log.warning(format, *args)
    +        else:
    +            app_log.error(  # type: ignore
    +                "Uncaught exception %s\n%r",
    +                self._request_summary(),
    +                self.request,
    +                exc_info=(typ, value, tb),
    +            )
    +
    +    def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]:
    +        def render(*args, **kwargs) -> str:  # type: ignore
    +            if not hasattr(self, "_active_modules"):
    +                self._active_modules = {}  # type: Dict[str, UIModule]
    +            if name not in self._active_modules:
    +                self._active_modules[name] = module(self)
    +            rendered = self._active_modules[name].render(*args, **kwargs)
    +            return rendered
    +
    +        return render
    +
    +    def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:
    +        return lambda *args, **kwargs: method(self, *args, **kwargs)
    +
    +    def _clear_headers_for_304(self) -> None:
    +        # 304 responses should not contain entity headers (defined in
    +        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1)
    +        # not explicitly allowed by
    +        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
    +        headers = [
    +            "Allow",
    +            "Content-Encoding",
    +            "Content-Language",
    +            "Content-Length",
    +            "Content-MD5",
    +            "Content-Range",
    +            "Content-Type",
    +            "Last-Modified",
    +        ]
    +        for h in headers:
    +            self.clear_header(h)
    +
    +
    +def stream_request_body(cls: Type[RequestHandler]) -> Type[RequestHandler]:
    +    """Apply to `RequestHandler` subclasses to enable streaming body support.
    +
    +    This decorator implies the following changes:
    +
    +    * `.HTTPServerRequest.body` is undefined, and body arguments will not
    +      be included in `RequestHandler.get_argument`.
    +    * `RequestHandler.prepare` is called when the request headers have been
    +      read instead of after the entire body has been read.
    +    * The subclass must define a method ``data_received(self, data):``, which
    +      will be called zero or more times as data is available.  Note that
    +      if the request has an empty body, ``data_received`` may not be called.
    +    * ``prepare`` and ``data_received`` may return Futures (such as via
    +      ``@gen.coroutine``, in which case the next method will not be called
    +      until those futures have completed.
    +    * The regular HTTP method (``post``, ``put``, etc) will be called after
    +      the entire body has been read.
    +
    +    See the `file receiver demo `_
    +    for example usage.
    +    """  # noqa: E501
    +    if not issubclass(cls, RequestHandler):
    +        raise TypeError("expected subclass of RequestHandler, got %r", cls)
    +    cls._stream_request_body = True
    +    return cls
    +
    +
    +def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
    +    if not issubclass(cls, RequestHandler):
    +        raise TypeError("expected subclass of RequestHandler, got %r", cls)
    +    return cls._stream_request_body
    +
    +
    +def removeslash(
    +    method: Callable[..., Optional[Awaitable[None]]]
    +) -> Callable[..., Optional[Awaitable[None]]]:
    +    """Use this decorator to remove trailing slashes from the request path.
    +
    +    For example, a request to ``/foo/`` would redirect to ``/foo`` with this
    +    decorator. Your request handler mapping should use a regular expression
    +    like ``r'/foo/*'`` in conjunction with using the decorator.
    +    """
    +
    +    @functools.wraps(method)
    +    def wrapper(  # type: ignore
    +        self: RequestHandler, *args, **kwargs
    +    ) -> Optional[Awaitable[None]]:
    +        if self.request.path.endswith("/"):
    +            if self.request.method in ("GET", "HEAD"):
    +                uri = self.request.path.rstrip("/")
    +                if uri:  # don't try to redirect '/' to ''
    +                    if self.request.query:
    +                        uri += "?" + self.request.query
    +                    self.redirect(uri, permanent=True)
    +                    return None
    +            else:
    +                raise HTTPError(404)
    +        return method(self, *args, **kwargs)
    +
    +    return wrapper
    +
    +
    +def addslash(
    +    method: Callable[..., Optional[Awaitable[None]]]
    +) -> Callable[..., Optional[Awaitable[None]]]:
    +    """Use this decorator to add a missing trailing slash to the request path.
    +
    +    For example, a request to ``/foo`` would redirect to ``/foo/`` with this
    +    decorator. Your request handler mapping should use a regular expression
    +    like ``r'/foo/?'`` in conjunction with using the decorator.
    +    """
    +
    +    @functools.wraps(method)
    +    def wrapper(  # type: ignore
    +        self: RequestHandler, *args, **kwargs
    +    ) -> Optional[Awaitable[None]]:
    +        if not self.request.path.endswith("/"):
    +            if self.request.method in ("GET", "HEAD"):
    +                uri = self.request.path + "/"
    +                if self.request.query:
    +                    uri += "?" + self.request.query
    +                self.redirect(uri, permanent=True)
    +                return None
    +            raise HTTPError(404)
    +        return method(self, *args, **kwargs)
    +
    +    return wrapper
    +
    +
    +class _ApplicationRouter(ReversibleRuleRouter):
    +    """Routing implementation used internally by `Application`.
    +
    +    Provides a binding between `Application` and `RequestHandler`.
    +    This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
    +        * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
    +        * it allows to use a list/tuple of rules as `~.routing.Rule` target.
    +        ``process_rule`` implementation will substitute this list with an appropriate
    +        `_ApplicationRouter` instance.
    +    """
    +
    +    def __init__(self, application: "Application", rules: _RuleList = None) -> None:
    +        assert isinstance(application, Application)
    +        self.application = application
    +        super(_ApplicationRouter, self).__init__(rules)
    +
    +    def process_rule(self, rule: Rule) -> Rule:
    +        rule = super(_ApplicationRouter, self).process_rule(rule)
    +
    +        if isinstance(rule.target, (list, tuple)):
    +            rule.target = _ApplicationRouter(  # type: ignore
    +                self.application, rule.target
    +            )
    +
    +        return rule
    +
    +    def get_target_delegate(
    +        self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
    +    ) -> Optional[httputil.HTTPMessageDelegate]:
    +        if isclass(target) and issubclass(target, RequestHandler):
    +            return self.application.get_handler_delegate(
    +                request, target, **target_params
    +            )
    +
    +        return super(_ApplicationRouter, self).get_target_delegate(
    +            target, request, **target_params
    +        )
    +
    +
    +class Application(ReversibleRouter):
    +    r"""A collection of request handlers that make up a web application.
    +
    +    Instances of this class are callable and can be passed directly to
    +    HTTPServer to serve the application::
    +
    +        application = web.Application([
    +            (r"/", MainPageHandler),
    +        ])
    +        http_server = httpserver.HTTPServer(application)
    +        http_server.listen(8080)
    +        ioloop.IOLoop.current().start()
    +
    +    The constructor for this class takes in a list of `~.routing.Rule`
    +    objects or tuples of values corresponding to the arguments of
    +    `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,
    +    the values in square brackets being optional. The default matcher is
    +    `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used
    +    instead of ``(PathMatches(regexp), target)``.
    +
    +    A common routing target is a `RequestHandler` subclass, but you can also
    +    use lists of rules as a target, which create a nested routing configuration::
    +
    +        application = web.Application([
    +            (HostMatches("example.com"), [
    +                (r"/", MainPageHandler),
    +                (r"/feed", FeedHandler),
    +            ]),
    +        ])
    +
    +    In addition to this you can use nested `~.routing.Router` instances,
    +    `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets
    +    (see `~.routing` module docs for more information).
    +
    +    When we receive requests, we iterate over the list in order and
    +    instantiate an instance of the first request class whose regexp
    +    matches the request path. The request class can be specified as
    +    either a class object or a (fully-qualified) name.
    +
    +    A dictionary may be passed as the third element (``target_kwargs``)
    +    of the tuple, which will be used as keyword arguments to the handler's
    +    constructor and `~RequestHandler.initialize` method. This pattern
    +    is used for the `StaticFileHandler` in this example (note that a
    +    `StaticFileHandler` can be installed automatically with the
    +    static_path setting described below)::
    +
    +        application = web.Application([
    +            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
    +        ])
    +
    +    We support virtual hosts with the `add_handlers` method, which takes in
    +    a host regular expression as the first argument::
    +
    +        application.add_handlers(r"www\.myhost\.com", [
    +            (r"/article/([0-9]+)", ArticleHandler),
    +        ])
    +
    +    If there's no match for the current request's host, then ``default_host``
    +    parameter value is matched against host regular expressions.
    +
    +
    +    .. warning::
    +
    +       Applications that do not use TLS may be vulnerable to :ref:`DNS
    +       rebinding ` attacks. This attack is especially
    +       relevant to applications that only listen on ``127.0.0.1`` or
    +       other private networks. Appropriate host patterns must be used
    +       (instead of the default of ``r'.*'``) to prevent this risk. The
    +       ``default_host`` argument must not be used in applications that
    +       may be vulnerable to DNS rebinding.
    +
    +    You can serve static files by sending the ``static_path`` setting
    +    as a keyword argument. We will serve those files from the
    +    ``/static/`` URI (this is configurable with the
    +    ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
    +    and ``/robots.txt`` from the same directory.  A custom subclass of
    +    `StaticFileHandler` can be specified with the
    +    ``static_handler_class`` setting.
    +
    +    .. versionchanged:: 4.5
    +       Integration with the new `tornado.routing` module.
    +
    +    """
    +
    +    def __init__(
    +        self,
    +        handlers: _RuleList = None,
    +        default_host: str = None,
    +        transforms: List[Type["OutputTransform"]] = None,
    +        **settings: Any
    +    ) -> None:
    +        if transforms is None:
    +            self.transforms = []  # type: List[Type[OutputTransform]]
    +            if settings.get("compress_response") or settings.get("gzip"):
    +                self.transforms.append(GZipContentEncoding)
    +        else:
    +            self.transforms = transforms
    +        self.default_host = default_host
    +        self.settings = settings
    +        self.ui_modules = {
    +            "linkify": _linkify,
    +            "xsrf_form_html": _xsrf_form_html,
    +            "Template": TemplateModule,
    +        }
    +        self.ui_methods = {}  # type: Dict[str, Callable[..., str]]
    +        self._load_ui_modules(settings.get("ui_modules", {}))
    +        self._load_ui_methods(settings.get("ui_methods", {}))
    +        if self.settings.get("static_path"):
    +            path = self.settings["static_path"]
    +            handlers = list(handlers or [])
    +            static_url_prefix = settings.get("static_url_prefix", "/static/")
    +            static_handler_class = settings.get(
    +                "static_handler_class", StaticFileHandler
    +            )
    +            static_handler_args = settings.get("static_handler_args", {})
    +            static_handler_args["path"] = path
    +            for pattern in [
    +                re.escape(static_url_prefix) + r"(.*)",
    +                r"/(favicon\.ico)",
    +                r"/(robots\.txt)",
    +            ]:
    +                handlers.insert(0, (pattern, static_handler_class, static_handler_args))
    +
    +        if self.settings.get("debug"):
    +            self.settings.setdefault("autoreload", True)
    +            self.settings.setdefault("compiled_template_cache", False)
    +            self.settings.setdefault("static_hash_cache", False)
    +            self.settings.setdefault("serve_traceback", True)
    +
    +        self.wildcard_router = _ApplicationRouter(self, handlers)
    +        self.default_router = _ApplicationRouter(
    +            self, [Rule(AnyMatches(), self.wildcard_router)]
    +        )
    +
    +        # Automatically reload modified modules
    +        if self.settings.get("autoreload"):
    +            from tornado import autoreload
    +
    +            autoreload.start()
    +
    +    def listen(self, port: int, address: str = "", **kwargs: Any) -> HTTPServer:
    +        """Starts an HTTP server for this application on the given port.
    +
    +        This is a convenience alias for creating an `.HTTPServer`
    +        object and calling its listen method.  Keyword arguments not
    +        supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the
    +        `.HTTPServer` constructor.  For advanced uses
    +        (e.g. multi-process mode), do not use this method; create an
    +        `.HTTPServer` and call its
    +        `.TCPServer.bind`/`.TCPServer.start` methods directly.
    +
    +        Note that after calling this method you still need to call
    +        ``IOLoop.current().start()`` to start the server.
    +
    +        Returns the `.HTTPServer` object.
    +
    +        .. versionchanged:: 4.3
    +           Now returns the `.HTTPServer` object.
    +        """
    +        server = HTTPServer(self, **kwargs)
    +        server.listen(port, address)
    +        return server
    +
    +    def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:
    +        """Appends the given handlers to our handler list.
    +
    +        Host patterns are processed sequentially in the order they were
    +        added. All matching patterns will be considered.
    +        """
    +        host_matcher = HostMatches(host_pattern)
    +        rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))
    +
    +        self.default_router.rules.insert(-1, rule)
    +
    +        if self.default_host is not None:
    +            self.wildcard_router.add_rules(
    +                [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]
    +            )
    +
    +    def add_transform(self, transform_class: Type["OutputTransform"]) -> None:
    +        self.transforms.append(transform_class)
    +
    +    def _load_ui_methods(self, methods: Any) -> None:
    +        if isinstance(methods, types.ModuleType):
    +            self._load_ui_methods(dict((n, getattr(methods, n)) for n in dir(methods)))
    +        elif isinstance(methods, list):
    +            for m in methods:
    +                self._load_ui_methods(m)
    +        else:
    +            for name, fn in methods.items():
    +                if (
    +                    not name.startswith("_")
    +                    and hasattr(fn, "__call__")
    +                    and name[0].lower() == name[0]
    +                ):
    +                    self.ui_methods[name] = fn
    +
    +    def _load_ui_modules(self, modules: Any) -> None:
    +        if isinstance(modules, types.ModuleType):
    +            self._load_ui_modules(dict((n, getattr(modules, n)) for n in dir(modules)))
    +        elif isinstance(modules, list):
    +            for m in modules:
    +                self._load_ui_modules(m)
    +        else:
    +            assert isinstance(modules, dict)
    +            for name, cls in modules.items():
    +                try:
    +                    if issubclass(cls, UIModule):
    +                        self.ui_modules[name] = cls
    +                except TypeError:
    +                    pass
    +
    +    def __call__(
    +        self, request: httputil.HTTPServerRequest
    +    ) -> Optional[Awaitable[None]]:
    +        # Legacy HTTPServer interface
    +        dispatcher = self.find_handler(request)
    +        return dispatcher.execute()
    +
    +    def find_handler(
    +        self, request: httputil.HTTPServerRequest, **kwargs: Any
    +    ) -> "_HandlerDelegate":
    +        route = self.default_router.find_handler(request)
    +        if route is not None:
    +            return cast("_HandlerDelegate", route)
    +
    +        if self.settings.get("default_handler_class"):
    +            return self.get_handler_delegate(
    +                request,
    +                self.settings["default_handler_class"],
    +                self.settings.get("default_handler_args", {}),
    +            )
    +
    +        return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404})
    +
    +    def get_handler_delegate(
    +        self,
    +        request: httputil.HTTPServerRequest,
    +        target_class: Type[RequestHandler],
    +        target_kwargs: Dict[str, Any] = None,
    +        path_args: List[bytes] = None,
    +        path_kwargs: Dict[str, bytes] = None,
    +    ) -> "_HandlerDelegate":
    +        """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
    +        for application and `RequestHandler` subclass.
    +
    +        :arg httputil.HTTPServerRequest request: current HTTP request.
    +        :arg RequestHandler target_class: a `RequestHandler` class.
    +        :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
    +        :arg list path_args: positional arguments for ``target_class`` HTTP method that
    +            will be executed while handling a request (``get``, ``post`` or any other).
    +        :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
    +        """
    +        return _HandlerDelegate(
    +            self, request, target_class, target_kwargs, path_args, path_kwargs
    +        )
    +
    +    def reverse_url(self, name: str, *args: Any) -> str:
    +        """Returns a URL path for handler named ``name``
    +
    +        The handler must be added to the application as a named `URLSpec`.
    +
    +        Args will be substituted for capturing groups in the `URLSpec` regex.
    +        They will be converted to strings if necessary, encoded as utf8,
    +        and url-escaped.
    +        """
    +        reversed_url = self.default_router.reverse_url(name, *args)
    +        if reversed_url is not None:
    +            return reversed_url
    +
    +        raise KeyError("%s not found in named urls" % name)
    +
    +    def log_request(self, handler: RequestHandler) -> None:
    +        """Writes a completed HTTP request to the logs.
    +
    +        By default writes to the python root logger.  To change
    +        this behavior either subclass Application and override this method,
    +        or pass a function in the application settings dictionary as
    +        ``log_function``.
    +        """
    +        if "log_function" in self.settings:
    +            self.settings["log_function"](handler)
    +            return
    +        if handler.get_status() < 400:
    +            log_method = access_log.info
    +        elif handler.get_status() < 500:
    +            log_method = access_log.warning
    +        else:
    +            log_method = access_log.error
    +        request_time = 1000.0 * handler.request.request_time()
    +        log_method(
    +            "%d %s %.2fms",
    +            handler.get_status(),
    +            handler._request_summary(),
    +            request_time,
    +        )
    +
    +
    +class _HandlerDelegate(httputil.HTTPMessageDelegate):
    +    def __init__(
    +        self,
    +        application: Application,
    +        request: httputil.HTTPServerRequest,
    +        handler_class: Type[RequestHandler],
    +        handler_kwargs: Optional[Dict[str, Any]],
    +        path_args: Optional[List[bytes]],
    +        path_kwargs: Optional[Dict[str, bytes]],
    +    ) -> None:
    +        self.application = application
    +        self.connection = request.connection
    +        self.request = request
    +        self.handler_class = handler_class
    +        self.handler_kwargs = handler_kwargs or {}
    +        self.path_args = path_args or []
    +        self.path_kwargs = path_kwargs or {}
    +        self.chunks = []  # type: List[bytes]
    +        self.stream_request_body = _has_stream_request_body(self.handler_class)
    +
    +    def headers_received(
    +        self,
    +        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
    +        headers: httputil.HTTPHeaders,
    +    ) -> Optional[Awaitable[None]]:
    +        if self.stream_request_body:
    +            self.request._body_future = Future()
    +            return self.execute()
    +        return None
    +
    +    def data_received(self, data: bytes) -> Optional[Awaitable[None]]:
    +        if self.stream_request_body:
    +            return self.handler.data_received(data)
    +        else:
    +            self.chunks.append(data)
    +            return None
    +
    +    def finish(self) -> None:
    +        if self.stream_request_body:
    +            future_set_result_unless_cancelled(self.request._body_future, None)
    +        else:
    +            self.request.body = b"".join(self.chunks)
    +            self.request._parse_body()
    +            self.execute()
    +
    +    def on_connection_close(self) -> None:
    +        if self.stream_request_body:
    +            self.handler.on_connection_close()
    +        else:
    +            self.chunks = None  # type: ignore
    +
    +    def execute(self) -> Optional[Awaitable[None]]:
    +        # If template cache is disabled (usually in the debug mode),
    +        # re-compile templates and reload static files on every
    +        # request so you don't need to restart to see changes
    +        if not self.application.settings.get("compiled_template_cache", True):
    +            with RequestHandler._template_loader_lock:
    +                for loader in RequestHandler._template_loaders.values():
    +                    loader.reset()
    +        if not self.application.settings.get("static_hash_cache", True):
    +            StaticFileHandler.reset()
    +
    +        self.handler = self.handler_class(
    +            self.application, self.request, **self.handler_kwargs
    +        )
    +        transforms = [t(self.request) for t in self.application.transforms]
    +
    +        if self.stream_request_body:
    +            self.handler._prepared_future = Future()
    +        # Note that if an exception escapes handler._execute it will be
    +        # trapped in the Future it returns (which we are ignoring here,
    +        # leaving it to be logged when the Future is GC'd).
    +        # However, that shouldn't happen because _execute has a blanket
    +        # except handler, and we cannot easily access the IOLoop here to
    +        # call add_future (because of the requirement to remain compatible
    +        # with WSGI)
    +        fut = gen.convert_yielded(
    +            self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
    +        )
    +        fut.add_done_callback(lambda f: f.result())
    +        # If we are streaming the request body, then execute() is finished
    +        # when the handler has prepared to receive the body.  If not,
    +        # it doesn't matter when execute() finishes (so we return None)
    +        return self.handler._prepared_future
    +
    +
    +class HTTPError(Exception):
    +    """An exception that will turn into an HTTP error response.
    +
    +    Raising an `HTTPError` is a convenient alternative to calling
    +    `RequestHandler.send_error` since it automatically ends the
    +    current function.
    +
    +    To customize the response sent with an `HTTPError`, override
    +    `RequestHandler.write_error`.
    +
    +    :arg int status_code: HTTP status code.  Must be listed in
    +        `httplib.responses ` unless the ``reason``
    +        keyword argument is given.
    +    :arg str log_message: Message to be written to the log for this error
    +        (will not be shown to the user unless the `Application` is in debug
    +        mode).  May contain ``%s``-style placeholders, which will be filled
    +        in with remaining positional parameters.
    +    :arg str reason: Keyword-only argument.  The HTTP "reason" phrase
    +        to pass in the status line along with ``status_code``.  Normally
    +        determined automatically from ``status_code``, but can be used
    +        to use a non-standard numeric code.
    +    """
    +
    +    def __init__(
    +        self, status_code: int = 500, log_message: str = None, *args: Any, **kwargs: Any
    +    ) -> None:
    +        self.status_code = status_code
    +        self.log_message = log_message
    +        self.args = args
    +        self.reason = kwargs.get("reason", None)
    +        if log_message and not args:
    +            self.log_message = log_message.replace("%", "%%")
    +
    +    def __str__(self) -> str:
    +        message = "HTTP %d: %s" % (
    +            self.status_code,
    +            self.reason or httputil.responses.get(self.status_code, "Unknown"),
    +        )
    +        if self.log_message:
    +            return message + " (" + (self.log_message % self.args) + ")"
    +        else:
    +            return message
    +
    +
    +class Finish(Exception):
    +    """An exception that ends the request without producing an error response.
    +
    +    When `Finish` is raised in a `RequestHandler`, the request will
    +    end (calling `RequestHandler.finish` if it hasn't already been
    +    called), but the error-handling methods (including
    +    `RequestHandler.write_error`) will not be called.
    +
    +    If `Finish()` was created with no arguments, the pending response
    +    will be sent as-is. If `Finish()` was given an argument, that
    +    argument will be passed to `RequestHandler.finish()`.
    +
    +    This can be a more convenient way to implement custom error pages
    +    than overriding ``write_error`` (especially in library code)::
    +
    +        if self.current_user is None:
    +            self.set_status(401)
    +            self.set_header('WWW-Authenticate', 'Basic realm="something"')
    +            raise Finish()
    +
    +    .. versionchanged:: 4.3
    +       Arguments passed to ``Finish()`` will be passed on to
    +       `RequestHandler.finish`.
    +    """
    +
    +    pass
    +
    +
    +class MissingArgumentError(HTTPError):
    +    """Exception raised by `RequestHandler.get_argument`.
    +
    +    This is a subclass of `HTTPError`, so if it is uncaught a 400 response
    +    code will be used instead of 500 (and a stack trace will not be logged).
    +
    +    .. versionadded:: 3.1
    +    """
    +
    +    def __init__(self, arg_name: str) -> None:
    +        super(MissingArgumentError, self).__init__(
    +            400, "Missing argument %s" % arg_name
    +        )
    +        self.arg_name = arg_name
    +
    +
    +class ErrorHandler(RequestHandler):
    +    """Generates an error response with ``status_code`` for all requests."""
    +
    +    def initialize(self, status_code: int) -> None:
    +        self.set_status(status_code)
    +
    +    def prepare(self) -> None:
    +        raise HTTPError(self._status_code)
    +
    +    def check_xsrf_cookie(self) -> None:
    +        # POSTs to an ErrorHandler don't actually have side effects,
    +        # so we don't need to check the xsrf token.  This allows POSTs
    +        # to the wrong url to return a 404 instead of 403.
    +        pass
    +
    +
    +class RedirectHandler(RequestHandler):
    +    """Redirects the client to the given URL for all GET requests.
    +
    +    You should provide the keyword argument ``url`` to the handler, e.g.::
    +
    +        application = web.Application([
    +            (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
    +        ])
    +
    +    `RedirectHandler` supports regular expression substitutions. E.g., to
    +    swap the first and second parts of a path while preserving the remainder::
    +
    +        application = web.Application([
    +            (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
    +        ])
    +
    +    The final URL is formatted with `str.format` and the substrings that match
    +    the capturing groups. In the above example, a request to "/a/b/c" would be
    +    formatted like::
    +
    +        str.format("/{1}/{0}/{2}", "a", "b", "c")  # -> "/b/a/c"
    +
    +    Use Python's :ref:`format string syntax ` to customize how
    +    values are substituted.
    +
    +    .. versionchanged:: 4.5
    +       Added support for substitutions into the destination URL.
    +
    +    .. versionchanged:: 5.0
    +       If any query arguments are present, they will be copied to the
    +       destination URL.
    +    """
    +
    +    def initialize(self, url: str, permanent: bool = True) -> None:
    +        self._url = url
    +        self._permanent = permanent
    +
    +    def get(self, *args: Any) -> None:
    +        to_url = self._url.format(*args)
    +        if self.request.query_arguments:
    +            # TODO: figure out typing for the next line.
    +            to_url = httputil.url_concat(
    +                to_url,
    +                list(httputil.qs_to_qsl(self.request.query_arguments)),  # type: ignore
    +            )
    +        self.redirect(to_url, permanent=self._permanent)
    +
    +
    +class StaticFileHandler(RequestHandler):
    +    """A simple handler that can serve static content from a directory.
    +
    +    A `StaticFileHandler` is configured automatically if you pass the
    +    ``static_path`` keyword argument to `Application`.  This handler
    +    can be customized with the ``static_url_prefix``, ``static_handler_class``,
    +    and ``static_handler_args`` settings.
    +
    +    To map an additional path to this handler for a static data directory
    +    you would add a line to your application like::
    +
    +        application = web.Application([
    +            (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
    +        ])
    +
    +    The handler constructor requires a ``path`` argument, which specifies the
    +    local root directory of the content to be served.
    +
    +    Note that a capture group in the regex is required to parse the value for
    +    the ``path`` argument to the get() method (different than the constructor
    +    argument above); see `URLSpec` for details.
    +
    +    To serve a file like ``index.html`` automatically when a directory is
    +    requested, set ``static_handler_args=dict(default_filename="index.html")``
    +    in your application settings, or add ``default_filename`` as an initializer
    +    argument for your ``StaticFileHandler``.
    +
    +    To maximize the effectiveness of browser caching, this class supports
    +    versioned urls (by default using the argument ``?v=``).  If a version
    +    is given, we instruct the browser to cache this file indefinitely.
    +    `make_static_url` (also available as `RequestHandler.static_url`) can
    +    be used to construct a versioned url.
    +
    +    This handler is intended primarily for use in development and light-duty
    +    file serving; for heavy traffic it will be more efficient to use
    +    a dedicated static file server (such as nginx or Apache).  We support
    +    the HTTP ``Accept-Ranges`` mechanism to return partial content (because
    +    some browsers require this functionality to be present to seek in
    +    HTML5 audio or video).
    +
    +    **Subclassing notes**
    +
    +    This class is designed to be extensible by subclassing, but because
    +    of the way static urls are generated with class methods rather than
    +    instance methods, the inheritance patterns are somewhat unusual.
    +    Be sure to use the ``@classmethod`` decorator when overriding a
    +    class method.  Instance methods may use the attributes ``self.path``
    +    ``self.absolute_path``, and ``self.modified``.
    +
    +    Subclasses should only override methods discussed in this section;
    +    overriding other methods is error-prone.  Overriding
    +    ``StaticFileHandler.get`` is particularly problematic due to the
    +    tight coupling with ``compute_etag`` and other methods.
    +
    +    To change the way static urls are generated (e.g. to match the behavior
    +    of another server or CDN), override `make_static_url`, `parse_url_path`,
    +    `get_cache_time`, and/or `get_version`.
    +
    +    To replace all interaction with the filesystem (e.g. to serve
    +    static content from a database), override `get_content`,
    +    `get_content_size`, `get_modified_time`, `get_absolute_path`, and
    +    `validate_absolute_path`.
    +
    +    .. versionchanged:: 3.1
    +       Many of the methods for subclasses were added in Tornado 3.1.
    +    """
    +
    +    CACHE_MAX_AGE = 86400 * 365 * 10  # 10 years
    +
    +    _static_hashes = {}  # type: Dict[str, Optional[str]]
    +    _lock = threading.Lock()  # protects _static_hashes
    +
    +    def initialize(self, path: str, default_filename: str = None) -> None:
    +        self.root = path
    +        self.default_filename = default_filename
    +
    +    @classmethod
    +    def reset(cls) -> None:
    +        with cls._lock:
    +            cls._static_hashes = {}
    +
    +    def head(self, path: str) -> Awaitable[None]:
    +        return self.get(path, include_body=False)
    +
    +    async def get(self, path: str, include_body: bool = True) -> None:
    +        # Set up our path instance variables.
    +        self.path = self.parse_url_path(path)
    +        del path  # make sure we don't refer to path instead of self.path again
    +        absolute_path = self.get_absolute_path(self.root, self.path)
    +        self.absolute_path = self.validate_absolute_path(self.root, absolute_path)
    +        if self.absolute_path is None:
    +            return
    +
    +        self.modified = self.get_modified_time()
    +        self.set_headers()
    +
    +        if self.should_return_304():
    +            self.set_status(304)
    +            return
    +
    +        request_range = None
    +        range_header = self.request.headers.get("Range")
    +        if range_header:
    +            # As per RFC 2616 14.16, if an invalid Range header is specified,
    +            # the request will be treated as if the header didn't exist.
    +            request_range = httputil._parse_request_range(range_header)
    +
    +        size = self.get_content_size()
    +        if request_range:
    +            start, end = request_range
    +            if start is not None and start < 0:
    +                start += size
    +                if start < 0:
    +                    start = 0
    +            if (
    +                start is not None
    +                and (start >= size or (end is not None and start >= end))
    +            ) or end == 0:
    +                # As per RFC 2616 14.35.1, a range is not satisfiable only: if
    +                # the first requested byte is equal to or greater than the
    +                # content, or when a suffix with length 0 is specified.
    +                # https://tools.ietf.org/html/rfc7233#section-2.1
    +                # A byte-range-spec is invalid if the last-byte-pos value is present
    +                # and less than the first-byte-pos.
    +                self.set_status(416)  # Range Not Satisfiable
    +                self.set_header("Content-Type", "text/plain")
    +                self.set_header("Content-Range", "bytes */%s" % (size,))
    +                return
    +            if end is not None and end > size:
    +                # Clients sometimes blindly use a large range to limit their
    +                # download size; cap the endpoint at the actual file size.
    +                end = size
    +            # Note: only return HTTP 206 if less than the entire range has been
    +            # requested. Not only is this semantically correct, but Chrome
    +            # refuses to play audio if it gets an HTTP 206 in response to
    +            # ``Range: bytes=0-``.
    +            if size != (end or size) - (start or 0):
    +                self.set_status(206)  # Partial Content
    +                self.set_header(
    +                    "Content-Range", httputil._get_content_range(start, end, size)
    +                )
    +        else:
    +            start = end = None
    +
    +        if start is not None and end is not None:
    +            content_length = end - start
    +        elif end is not None:
    +            content_length = end
    +        elif start is not None:
    +            content_length = size - start
    +        else:
    +            content_length = size
    +        self.set_header("Content-Length", content_length)
    +
    +        if include_body:
    +            content = self.get_content(self.absolute_path, start, end)
    +            if isinstance(content, bytes):
    +                content = [content]
    +            for chunk in content:
    +                try:
    +                    self.write(chunk)
    +                    await self.flush()
    +                except iostream.StreamClosedError:
    +                    return
    +        else:
    +            assert self.request.method == "HEAD"
    +
    +    def compute_etag(self) -> Optional[str]:
    +        """Sets the ``Etag`` header based on static url version.
    +
    +        This allows efficient ``If-None-Match`` checks against cached
    +        versions, and sends the correct ``Etag`` for a partial response
    +        (i.e. the same ``Etag`` as the full file).
    +
    +        .. versionadded:: 3.1
    +        """
    +        assert self.absolute_path is not None
    +        version_hash = self._get_cached_version(self.absolute_path)
    +        if not version_hash:
    +            return None
    +        return '"%s"' % (version_hash,)
    +
    +    def set_headers(self) -> None:
    +        """Sets the content and caching headers on the response.
    +
    +        .. versionadded:: 3.1
    +        """
    +        self.set_header("Accept-Ranges", "bytes")
    +        self.set_etag_header()
    +
    +        if self.modified is not None:
    +            self.set_header("Last-Modified", self.modified)
    +
    +        content_type = self.get_content_type()
    +        if content_type:
    +            self.set_header("Content-Type", content_type)
    +
    +        cache_time = self.get_cache_time(self.path, self.modified, content_type)
    +        if cache_time > 0:
    +            self.set_header(
    +                "Expires",
    +                datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time),
    +            )
    +            self.set_header("Cache-Control", "max-age=" + str(cache_time))
    +
    +        self.set_extra_headers(self.path)
    +
    +    def should_return_304(self) -> bool:
    +        """Returns True if the headers indicate that we should return 304.
    +
    +        .. versionadded:: 3.1
    +        """
    +        # If client sent If-None-Match, use it, ignore If-Modified-Since
    +        if self.request.headers.get("If-None-Match"):
    +            return self.check_etag_header()
    +
    +        # Check the If-Modified-Since, and don't send the result if the
    +        # content has not been modified
    +        ims_value = self.request.headers.get("If-Modified-Since")
    +        if ims_value is not None:
    +            date_tuple = email.utils.parsedate(ims_value)
    +            if date_tuple is not None:
    +                if_since = datetime.datetime(*date_tuple[:6])
    +                assert self.modified is not None
    +                if if_since >= self.modified:
    +                    return True
    +
    +        return False
    +
    +    @classmethod
    +    def get_absolute_path(cls, root: str, path: str) -> str:
    +        """Returns the absolute location of ``path`` relative to ``root``.
    +
    +        ``root`` is the path configured for this `StaticFileHandler`
    +        (in most cases the ``static_path`` `Application` setting).
    +
    +        This class method may be overridden in subclasses.  By default
    +        it returns a filesystem path, but other strings may be used
    +        as long as they are unique and understood by the subclass's
    +        overridden `get_content`.
    +
    +        .. versionadded:: 3.1
    +        """
    +        abspath = os.path.abspath(os.path.join(root, path))
    +        return abspath
    +
    +    def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
    +        """Validate and return the absolute path.
    +
    +        ``root`` is the configured path for the `StaticFileHandler`,
    +        and ``path`` is the result of `get_absolute_path`
    +
    +        This is an instance method called during request processing,
    +        so it may raise `HTTPError` or use methods like
    +        `RequestHandler.redirect` (return None after redirecting to
    +        halt further processing).  This is where 404 errors for missing files
    +        are generated.
    +
    +        This method may modify the path before returning it, but note that
    +        any such modifications will not be understood by `make_static_url`.
    +
    +        In instance methods, this method's result is available as
    +        ``self.absolute_path``.
    +
    +        .. versionadded:: 3.1
    +        """
    +        # os.path.abspath strips a trailing /.
    +        # We must add it back to `root` so that we only match files
    +        # in a directory named `root` instead of files starting with
    +        # that prefix.
    +        root = os.path.abspath(root)
    +        if not root.endswith(os.path.sep):
    +            # abspath always removes a trailing slash, except when
    +            # root is '/'. This is an unusual case, but several projects
    +            # have independently discovered this technique to disable
    +            # Tornado's path validation and (hopefully) do their own,
    +            # so we need to support it.
    +            root += os.path.sep
    +        # The trailing slash also needs to be temporarily added back
    +        # the requested path so a request to root/ will match.
    +        if not (absolute_path + os.path.sep).startswith(root):
    +            raise HTTPError(403, "%s is not in root static directory", self.path)
    +        if os.path.isdir(absolute_path) and self.default_filename is not None:
    +            # need to look at the request.path here for when path is empty
    +            # but there is some prefix to the path that was already
    +            # trimmed by the routing
    +            if not self.request.path.endswith("/"):
    +                self.redirect(self.request.path + "/", permanent=True)
    +                return None
    +            absolute_path = os.path.join(absolute_path, self.default_filename)
    +        if not os.path.exists(absolute_path):
    +            raise HTTPError(404)
    +        if not os.path.isfile(absolute_path):
    +            raise HTTPError(403, "%s is not a file", self.path)
    +        return absolute_path
    +
    +    @classmethod
    +    def get_content(
    +        cls, abspath: str, start: int = None, end: int = None
    +    ) -> Generator[bytes, None, None]:
    +        """Retrieve the content of the requested resource which is located
    +        at the given absolute path.
    +
    +        This class method may be overridden by subclasses.  Note that its
    +        signature is different from other overridable class methods
    +        (no ``settings`` argument); this is deliberate to ensure that
    +        ``abspath`` is able to stand on its own as a cache key.
    +
    +        This method should either return a byte string or an iterator
    +        of byte strings.  The latter is preferred for large files
    +        as it helps reduce memory fragmentation.
    +
    +        .. versionadded:: 3.1
    +        """
    +        with open(abspath, "rb") as file:
    +            if start is not None:
    +                file.seek(start)
    +            if end is not None:
    +                remaining = end - (start or 0)  # type: Optional[int]
    +            else:
    +                remaining = None
    +            while True:
    +                chunk_size = 64 * 1024
    +                if remaining is not None and remaining < chunk_size:
    +                    chunk_size = remaining
    +                chunk = file.read(chunk_size)
    +                if chunk:
    +                    if remaining is not None:
    +                        remaining -= len(chunk)
    +                    yield chunk
    +                else:
    +                    if remaining is not None:
    +                        assert remaining == 0
    +                    return
    +
    +    @classmethod
    +    def get_content_version(cls, abspath: str) -> str:
    +        """Returns a version string for the resource at the given path.
    +
    +        This class method may be overridden by subclasses.  The
    +        default implementation is a hash of the file's contents.
    +
    +        .. versionadded:: 3.1
    +        """
    +        data = cls.get_content(abspath)
    +        hasher = hashlib.md5()
    +        if isinstance(data, bytes):
    +            hasher.update(data)
    +        else:
    +            for chunk in data:
    +                hasher.update(chunk)
    +        return hasher.hexdigest()
    +
    +    def _stat(self) -> os.stat_result:
    +        assert self.absolute_path is not None
    +        if not hasattr(self, "_stat_result"):
    +            self._stat_result = os.stat(self.absolute_path)
    +        return self._stat_result
    +
    +    def get_content_size(self) -> int:
    +        """Retrieve the total size of the resource at the given path.
    +
    +        This method may be overridden by subclasses.
    +
    +        .. versionadded:: 3.1
    +
    +        .. versionchanged:: 4.0
    +           This method is now always called, instead of only when
    +           partial results are requested.
    +        """
    +        stat_result = self._stat()
    +        return stat_result.st_size
    +
    +    def get_modified_time(self) -> Optional[datetime.datetime]:
    +        """Returns the time that ``self.absolute_path`` was last modified.
    +
    +        May be overridden in subclasses.  Should return a `~datetime.datetime`
    +        object or None.
    +
    +        .. versionadded:: 3.1
    +        """
    +        stat_result = self._stat()
    +        # NOTE: Historically, this used stat_result[stat.ST_MTIME],
    +        # which truncates the fractional portion of the timestamp. It
    +        # was changed from that form to stat_result.st_mtime to
    +        # satisfy mypy (which disallows the bracket operator), but the
    +        # latter form returns a float instead of an int. For
    +        # consistency with the past (and because we have a unit test
    +        # that relies on this), we truncate the float here, although
    +        # I'm not sure that's the right thing to do.
    +        modified = datetime.datetime.utcfromtimestamp(int(stat_result.st_mtime))
    +        return modified
    +
    +    def get_content_type(self) -> str:
    +        """Returns the ``Content-Type`` header to be used for this request.
    +
    +        .. versionadded:: 3.1
    +        """
    +        assert self.absolute_path is not None
    +        mime_type, encoding = mimetypes.guess_type(self.absolute_path)
    +        # per RFC 6713, use the appropriate type for a gzip compressed file
    +        if encoding == "gzip":
    +            return "application/gzip"
    +        # As of 2015-07-21 there is no bzip2 encoding defined at
    +        # http://www.iana.org/assignments/media-types/media-types.xhtml
    +        # So for that (and any other encoding), use octet-stream.
    +        elif encoding is not None:
    +            return "application/octet-stream"
    +        elif mime_type is not None:
    +            return mime_type
    +        # if mime_type not detected, use application/octet-stream
    +        else:
    +            return "application/octet-stream"
    +
    +    def set_extra_headers(self, path: str) -> None:
    +        """For subclass to add extra headers to the response"""
    +        pass
    +
    +    def get_cache_time(
    +        self, path: str, modified: Optional[datetime.datetime], mime_type: str
    +    ) -> int:
    +        """Override to customize cache control behavior.
    +
    +        Return a positive number of seconds to make the result
    +        cacheable for that amount of time or 0 to mark resource as
    +        cacheable for an unspecified amount of time (subject to
    +        browser heuristics).
    +
    +        By default returns cache expiry of 10 years for resources requested
    +        with ``v`` argument.
    +        """
    +        return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
    +
    +    @classmethod
    +    def make_static_url(
    +        cls, settings: Dict[str, Any], path: str, include_version: bool = True
    +    ) -> str:
    +        """Constructs a versioned url for the given path.
    +
    +        This method may be overridden in subclasses (but note that it
    +        is a class method rather than an instance method).  Subclasses
    +        are only required to implement the signature
    +        ``make_static_url(cls, settings, path)``; other keyword
    +        arguments may be passed through `~RequestHandler.static_url`
    +        but are not standard.
    +
    +        ``settings`` is the `Application.settings` dictionary.  ``path``
    +        is the static path being requested.  The url returned should be
    +        relative to the current host.
    +
    +        ``include_version`` determines whether the generated URL should
    +        include the query string containing the version hash of the
    +        file corresponding to the given ``path``.
    +
    +        """
    +        url = settings.get("static_url_prefix", "/static/") + path
    +        if not include_version:
    +            return url
    +
    +        version_hash = cls.get_version(settings, path)
    +        if not version_hash:
    +            return url
    +
    +        return "%s?v=%s" % (url, version_hash)
    +
    +    def parse_url_path(self, url_path: str) -> str:
    +        """Converts a static URL path into a filesystem path.
    +
    +        ``url_path`` is the path component of the URL with
    +        ``static_url_prefix`` removed.  The return value should be
    +        filesystem path relative to ``static_path``.
    +
    +        This is the inverse of `make_static_url`.
    +        """
    +        if os.path.sep != "/":
    +            url_path = url_path.replace("/", os.path.sep)
    +        return url_path
    +
    +    @classmethod
    +    def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]:
    +        """Generate the version string to be used in static URLs.
    +
    +        ``settings`` is the `Application.settings` dictionary and ``path``
    +        is the relative location of the requested asset on the filesystem.
    +        The returned value should be a string, or ``None`` if no version
    +        could be determined.
    +
    +        .. versionchanged:: 3.1
    +           This method was previously recommended for subclasses to override;
    +           `get_content_version` is now preferred as it allows the base
    +           class to handle caching of the result.
    +        """
    +        abs_path = cls.get_absolute_path(settings["static_path"], path)
    +        return cls._get_cached_version(abs_path)
    +
    +    @classmethod
    +    def _get_cached_version(cls, abs_path: str) -> Optional[str]:
    +        with cls._lock:
    +            hashes = cls._static_hashes
    +            if abs_path not in hashes:
    +                try:
    +                    hashes[abs_path] = cls.get_content_version(abs_path)
    +                except Exception:
    +                    gen_log.error("Could not open static file %r", abs_path)
    +                    hashes[abs_path] = None
    +            hsh = hashes.get(abs_path)
    +            if hsh:
    +                return hsh
    +        return None
    +
    +
    +class FallbackHandler(RequestHandler):
    +    """A `RequestHandler` that wraps another HTTP server callback.
    +
    +    The fallback is a callable object that accepts an
    +    `~.httputil.HTTPServerRequest`, such as an `Application` or
    +    `tornado.wsgi.WSGIContainer`.  This is most useful to use both
    +    Tornado ``RequestHandlers`` and WSGI in the same server.  Typical
    +    usage::
    +
    +        wsgi_app = tornado.wsgi.WSGIContainer(
    +            django.core.handlers.wsgi.WSGIHandler())
    +        application = tornado.web.Application([
    +            (r"/foo", FooHandler),
    +            (r".*", FallbackHandler, dict(fallback=wsgi_app),
    +        ])
    +    """
    +
    +    def initialize(
    +        self, fallback: Callable[[httputil.HTTPServerRequest], None]
    +    ) -> None:
    +        self.fallback = fallback
    +
    +    def prepare(self) -> None:
    +        self.fallback(self.request)
    +        self._finished = True
    +        self.on_finish()
    +
    +
    +class OutputTransform(object):
    +    """A transform modifies the result of an HTTP request (e.g., GZip encoding)
    +
    +    Applications are not expected to create their own OutputTransforms
    +    or interact with them directly; the framework chooses which transforms
    +    (if any) to apply.
    +    """
    +
    +    def __init__(self, request: httputil.HTTPServerRequest) -> None:
    +        pass
    +
    +    def transform_first_chunk(
    +        self,
    +        status_code: int,
    +        headers: httputil.HTTPHeaders,
    +        chunk: bytes,
    +        finishing: bool,
    +    ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
    +        return status_code, headers, chunk
    +
    +    def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
    +        return chunk
    +
    +
    +class GZipContentEncoding(OutputTransform):
    +    """Applies the gzip content encoding to the response.
    +
    +    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
    +
    +    .. versionchanged:: 4.0
    +        Now compresses all mime types beginning with ``text/``, instead
    +        of just a whitelist. (the whitelist is still used for certain
    +        non-text mime types).
    +    """
    +
    +    # Whitelist of compressible mime types (in addition to any types
    +    # beginning with "text/").
    +    CONTENT_TYPES = set(
    +        [
    +            "application/javascript",
    +            "application/x-javascript",
    +            "application/xml",
    +            "application/atom+xml",
    +            "application/json",
    +            "application/xhtml+xml",
    +            "image/svg+xml",
    +        ]
    +    )
    +    # Python's GzipFile defaults to level 9, while most other gzip
    +    # tools (including gzip itself) default to 6, which is probably a
    +    # better CPU/size tradeoff.
    +    GZIP_LEVEL = 6
    +    # Responses that are too short are unlikely to benefit from gzipping
    +    # after considering the "Content-Encoding: gzip" header and the header
    +    # inside the gzip encoding.
    +    # Note that responses written in multiple chunks will be compressed
    +    # regardless of size.
    +    MIN_LENGTH = 1024
    +
    +    def __init__(self, request: httputil.HTTPServerRequest) -> None:
    +        self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
    +
    +    def _compressible_type(self, ctype: str) -> bool:
    +        return ctype.startswith("text/") or ctype in self.CONTENT_TYPES
    +
    +    def transform_first_chunk(
    +        self,
    +        status_code: int,
    +        headers: httputil.HTTPHeaders,
    +        chunk: bytes,
    +        finishing: bool,
    +    ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
    +        # TODO: can/should this type be inherited from the superclass?
    +        if "Vary" in headers:
    +            headers["Vary"] += ", Accept-Encoding"
    +        else:
    +            headers["Vary"] = "Accept-Encoding"
    +        if self._gzipping:
    +            ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
    +            self._gzipping = (
    +                self._compressible_type(ctype)
    +                and (not finishing or len(chunk) >= self.MIN_LENGTH)
    +                and ("Content-Encoding" not in headers)
    +            )
    +        if self._gzipping:
    +            headers["Content-Encoding"] = "gzip"
    +            self._gzip_value = BytesIO()
    +            self._gzip_file = gzip.GzipFile(
    +                mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL
    +            )
    +            chunk = self.transform_chunk(chunk, finishing)
    +            if "Content-Length" in headers:
    +                # The original content length is no longer correct.
    +                # If this is the last (and only) chunk, we can set the new
    +                # content-length; otherwise we remove it and fall back to
    +                # chunked encoding.
    +                if finishing:
    +                    headers["Content-Length"] = str(len(chunk))
    +                else:
    +                    del headers["Content-Length"]
    +        return status_code, headers, chunk
    +
    +    def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
    +        if self._gzipping:
    +            self._gzip_file.write(chunk)
    +            if finishing:
    +                self._gzip_file.close()
    +            else:
    +                self._gzip_file.flush()
    +            chunk = self._gzip_value.getvalue()
    +            self._gzip_value.truncate(0)
    +            self._gzip_value.seek(0)
    +        return chunk
    +
    +
    +def authenticated(
    +    method: Callable[..., Optional[Awaitable[None]]]
    +) -> Callable[..., Optional[Awaitable[None]]]:
    +    """Decorate methods with this to require that the user be logged in.
    +
    +    If the user is not logged in, they will be redirected to the configured
    +    `login url `.
    +
    +    If you configure a login url with a query parameter, Tornado will
    +    assume you know what you're doing and use it as-is.  If not, it
    +    will add a `next` parameter so the login page knows where to send
    +    you once you're logged in.
    +    """
    +
    +    @functools.wraps(method)
    +    def wrapper(  # type: ignore
    +        self: RequestHandler, *args, **kwargs
    +    ) -> Optional[Awaitable[None]]:
    +        if not self.current_user:
    +            if self.request.method in ("GET", "HEAD"):
    +                url = self.get_login_url()
    +                if "?" not in url:
    +                    if urllib.parse.urlsplit(url).scheme:
    +                        # if login url is absolute, make next absolute too
    +                        next_url = self.request.full_url()
    +                    else:
    +                        assert self.request.uri is not None
    +                        next_url = self.request.uri
    +                    url += "?" + urlencode(dict(next=next_url))
    +                self.redirect(url)
    +                return None
    +            raise HTTPError(403)
    +        return method(self, *args, **kwargs)
    +
    +    return wrapper
    +
    +
    +class UIModule(object):
    +    """A re-usable, modular UI unit on a page.
    +
    +    UI modules often execute additional queries, and they can include
    +    additional CSS and JavaScript that will be included in the output
    +    page, which is automatically inserted on page render.
    +
    +    Subclasses of UIModule must override the `render` method.
    +    """
    +
    +    def __init__(self, handler: RequestHandler) -> None:
    +        self.handler = handler
    +        self.request = handler.request
    +        self.ui = handler.ui
    +        self.locale = handler.locale
    +
    +    @property
    +    def current_user(self) -> Any:
    +        return self.handler.current_user
    +
    +    def render(self, *args: Any, **kwargs: Any) -> str:
    +        """Override in subclasses to return this module's output."""
    +        raise NotImplementedError()
    +
    +    def embedded_javascript(self) -> Optional[str]:
    +        """Override to return a JavaScript string
    +        to be embedded in the page."""
    +        return None
    +
    +    def javascript_files(self) -> Optional[Iterable[str]]:
    +        """Override to return a list of JavaScript files needed by this module.
    +
    +        If the return values are relative paths, they will be passed to
    +        `RequestHandler.static_url`; otherwise they will be used as-is.
    +        """
    +        return None
    +
    +    def embedded_css(self) -> Optional[str]:
    +        """Override to return a CSS string
    +        that will be embedded in the page."""
    +        return None
    +
    +    def css_files(self) -> Optional[Iterable[str]]:
    +        """Override to returns a list of CSS files required by this module.
    +
    +        If the return values are relative paths, they will be passed to
    +        `RequestHandler.static_url`; otherwise they will be used as-is.
    +        """
    +        return None
    +
    +    def html_head(self) -> Optional[str]:
    +        """Override to return an HTML string that will be put in the 
    +        element.
    +        """
    +        return None
    +
    +    def html_body(self) -> Optional[str]:
    +        """Override to return an HTML string that will be put at the end of
    +        the  element.
    +        """
    +        return None
    +
    +    def render_string(self, path: str, **kwargs: Any) -> bytes:
    +        """Renders a template and returns it as a string."""
    +        return self.handler.render_string(path, **kwargs)
    +
    +
    +class _linkify(UIModule):
    +    def render(self, text: str, **kwargs: Any) -> str:  # type: ignore
    +        return escape.linkify(text, **kwargs)
    +
    +
    +class _xsrf_form_html(UIModule):
    +    def render(self) -> str:  # type: ignore
    +        return self.handler.xsrf_form_html()
    +
    +
    +class TemplateModule(UIModule):
    +    """UIModule that simply renders the given template.
    +
    +    {% module Template("foo.html") %} is similar to {% include "foo.html" %},
    +    but the module version gets its own namespace (with kwargs passed to
    +    Template()) instead of inheriting the outer template's namespace.
    +
    +    Templates rendered through this module also get access to UIModule's
    +    automatic javascript/css features.  Simply call set_resources
    +    inside the template and give it keyword arguments corresponding to
    +    the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
    +    Note that these resources are output once per template file, not once
    +    per instantiation of the template, so they must not depend on
    +    any arguments to the template.
    +    """
    +
    +    def __init__(self, handler: RequestHandler) -> None:
    +        super(TemplateModule, self).__init__(handler)
    +        # keep resources in both a list and a dict to preserve order
    +        self._resource_list = []  # type: List[Dict[str, Any]]
    +        self._resource_dict = {}  # type: Dict[str, Dict[str, Any]]
    +
    +    def render(self, path: str, **kwargs: Any) -> bytes:  # type: ignore
    +        def set_resources(**kwargs) -> str:  # type: ignore
    +            if path not in self._resource_dict:
    +                self._resource_list.append(kwargs)
    +                self._resource_dict[path] = kwargs
    +            else:
    +                if self._resource_dict[path] != kwargs:
    +                    raise ValueError(
    +                        "set_resources called with different "
    +                        "resources for the same template"
    +                    )
    +            return ""
    +
    +        return self.render_string(path, set_resources=set_resources, **kwargs)
    +
    +    def _get_resources(self, key: str) -> Iterable[str]:
    +        return (r[key] for r in self._resource_list if key in r)
    +
    +    def embedded_javascript(self) -> str:
    +        return "\n".join(self._get_resources("embedded_javascript"))
    +
    +    def javascript_files(self) -> Iterable[str]:
    +        result = []
    +        for f in self._get_resources("javascript_files"):
    +            if isinstance(f, (unicode_type, bytes)):
    +                result.append(f)
    +            else:
    +                result.extend(f)
    +        return result
    +
    +    def embedded_css(self) -> str:
    +        return "\n".join(self._get_resources("embedded_css"))
    +
    +    def css_files(self) -> Iterable[str]:
    +        result = []
    +        for f in self._get_resources("css_files"):
    +            if isinstance(f, (unicode_type, bytes)):
    +                result.append(f)
    +            else:
    +                result.extend(f)
    +        return result
    +
    +    def html_head(self) -> str:
    +        return "".join(self._get_resources("html_head"))
    +
    +    def html_body(self) -> str:
    +        return "".join(self._get_resources("html_body"))
    +
    +
    +class _UIModuleNamespace(object):
    +    """Lazy namespace which creates UIModule proxies bound to a handler."""
    +
    +    def __init__(
    +        self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]]
    +    ) -> None:
    +        self.handler = handler
    +        self.ui_modules = ui_modules
    +
    +    def __getitem__(self, key: str) -> Callable[..., str]:
    +        return self.handler._ui_module(key, self.ui_modules[key])
    +
    +    def __getattr__(self, key: str) -> Callable[..., str]:
    +        try:
    +            return self[key]
    +        except KeyError as e:
    +            raise AttributeError(str(e))
    +
    +
    +def create_signed_value(
    +    secret: _CookieSecretTypes,
    +    name: str,
    +    value: Union[str, bytes],
    +    version: int = None,
    +    clock: Callable[[], float] = None,
    +    key_version: int = None,
    +) -> bytes:
    +    if version is None:
    +        version = DEFAULT_SIGNED_VALUE_VERSION
    +    if clock is None:
    +        clock = time.time
    +
    +    timestamp = utf8(str(int(clock())))
    +    value = base64.b64encode(utf8(value))
    +    if version == 1:
    +        assert not isinstance(secret, dict)
    +        signature = _create_signature_v1(secret, name, value, timestamp)
    +        value = b"|".join([value, timestamp, signature])
    +        return value
    +    elif version == 2:
    +        # The v2 format consists of a version number and a series of
    +        # length-prefixed fields "%d:%s", the last of which is a
    +        # signature, all separated by pipes.  All numbers are in
    +        # decimal format with no leading zeros.  The signature is an
    +        # HMAC-SHA256 of the whole string up to that point, including
    +        # the final pipe.
    +        #
    +        # The fields are:
    +        # - format version (i.e. 2; no length prefix)
    +        # - key version (integer, default is 0)
    +        # - timestamp (integer seconds since epoch)
    +        # - name (not encoded; assumed to be ~alphanumeric)
    +        # - value (base64-encoded)
    +        # - signature (hex-encoded; no length prefix)
    +        def format_field(s: Union[str, bytes]) -> bytes:
    +            return utf8("%d:" % len(s)) + utf8(s)
    +
    +        to_sign = b"|".join(
    +            [
    +                b"2",
    +                format_field(str(key_version or 0)),
    +                format_field(timestamp),
    +                format_field(name),
    +                format_field(value),
    +                b"",
    +            ]
    +        )
    +
    +        if isinstance(secret, dict):
    +            assert (
    +                key_version is not None
    +            ), "Key version must be set when sign key dict is used"
    +            assert version >= 2, "Version must be at least 2 for key version support"
    +            secret = secret[key_version]
    +
    +        signature = _create_signature_v2(secret, to_sign)
    +        return to_sign + signature
    +    else:
    +        raise ValueError("Unsupported version %d" % version)
    +
    +
    +# A leading version number in decimal
    +# with no leading zeros, followed by a pipe.
    +_signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$")
    +
    +
    +def _get_version(value: bytes) -> int:
    +    # Figures out what version value is.  Version 1 did not include an
    +    # explicit version field and started with arbitrary base64 data,
    +    # which makes this tricky.
    +    m = _signed_value_version_re.match(value)
    +    if m is None:
    +        version = 1
    +    else:
    +        try:
    +            version = int(m.group(1))
    +            if version > 999:
    +                # Certain payloads from the version-less v1 format may
    +                # be parsed as valid integers.  Due to base64 padding
    +                # restrictions, this can only happen for numbers whose
    +                # length is a multiple of 4, so we can treat all
    +                # numbers up to 999 as versions, and for the rest we
    +                # fall back to v1 format.
    +                version = 1
    +        except ValueError:
    +            version = 1
    +    return version
    +
    +
    +def decode_signed_value(
    +    secret: _CookieSecretTypes,
    +    name: str,
    +    value: Union[None, str, bytes],
    +    max_age_days: int = 31,
    +    clock: Callable[[], float] = None,
    +    min_version: int = None,
    +) -> Optional[bytes]:
    +    if clock is None:
    +        clock = time.time
    +    if min_version is None:
    +        min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
    +    if min_version > 2:
    +        raise ValueError("Unsupported min_version %d" % min_version)
    +    if not value:
    +        return None
    +
    +    value = utf8(value)
    +    version = _get_version(value)
    +
    +    if version < min_version:
    +        return None
    +    if version == 1:
    +        assert not isinstance(secret, dict)
    +        return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
    +    elif version == 2:
    +        return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
    +    else:
    +        return None
    +
    +
    +def _decode_signed_value_v1(
    +    secret: Union[str, bytes],
    +    name: str,
    +    value: bytes,
    +    max_age_days: int,
    +    clock: Callable[[], float],
    +) -> Optional[bytes]:
    +    parts = utf8(value).split(b"|")
    +    if len(parts) != 3:
    +        return None
    +    signature = _create_signature_v1(secret, name, parts[0], parts[1])
    +    if not hmac.compare_digest(parts[2], signature):
    +        gen_log.warning("Invalid cookie signature %r", value)
    +        return None
    +    timestamp = int(parts[1])
    +    if timestamp < clock() - max_age_days * 86400:
    +        gen_log.warning("Expired cookie %r", value)
    +        return None
    +    if timestamp > clock() + 31 * 86400:
    +        # _cookie_signature does not hash a delimiter between the
    +        # parts of the cookie, so an attacker could transfer trailing
    +        # digits from the payload to the timestamp without altering the
    +        # signature.  For backwards compatibility, sanity-check timestamp
    +        # here instead of modifying _cookie_signature.
    +        gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
    +        return None
    +    if parts[1].startswith(b"0"):
    +        gen_log.warning("Tampered cookie %r", value)
    +        return None
    +    try:
    +        return base64.b64decode(parts[0])
    +    except Exception:
    +        return None
    +
    +
    +def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]:
    +    def _consume_field(s: bytes) -> Tuple[bytes, bytes]:
    +        length, _, rest = s.partition(b":")
    +        n = int(length)
    +        field_value = rest[:n]
    +        # In python 3, indexing bytes returns small integers; we must
    +        # use a slice to get a byte string as in python 2.
    +        if rest[n : n + 1] != b"|":
    +            raise ValueError("malformed v2 signed value field")
    +        rest = rest[n + 1 :]
    +        return field_value, rest
    +
    +    rest = value[2:]  # remove version number
    +    key_version, rest = _consume_field(rest)
    +    timestamp, rest = _consume_field(rest)
    +    name_field, rest = _consume_field(rest)
    +    value_field, passed_sig = _consume_field(rest)
    +    return int(key_version), timestamp, name_field, value_field, passed_sig
    +
    +
    +def _decode_signed_value_v2(
    +    secret: _CookieSecretTypes,
    +    name: str,
    +    value: bytes,
    +    max_age_days: int,
    +    clock: Callable[[], float],
    +) -> Optional[bytes]:
    +    try:
    +        key_version, timestamp_bytes, name_field, value_field, passed_sig = _decode_fields_v2(
    +            value
    +        )
    +    except ValueError:
    +        return None
    +    signed_string = value[: -len(passed_sig)]
    +
    +    if isinstance(secret, dict):
    +        try:
    +            secret = secret[key_version]
    +        except KeyError:
    +            return None
    +
    +    expected_sig = _create_signature_v2(secret, signed_string)
    +    if not hmac.compare_digest(passed_sig, expected_sig):
    +        return None
    +    if name_field != utf8(name):
    +        return None
    +    timestamp = int(timestamp_bytes)
    +    if timestamp < clock() - max_age_days * 86400:
    +        # The signature has expired.
    +        return None
    +    try:
    +        return base64.b64decode(value_field)
    +    except Exception:
    +        return None
    +
    +
    +def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]:
    +    value = utf8(value)
    +    version = _get_version(value)
    +    if version < 2:
    +        return None
    +    try:
    +        key_version, _, _, _, _ = _decode_fields_v2(value)
    +    except ValueError:
    +        return None
    +
    +    return key_version
    +
    +
    +def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes:
    +    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
    +    for part in parts:
    +        hash.update(utf8(part))
    +    return utf8(hash.hexdigest())
    +
    +
    +def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes:
    +    hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
    +    hash.update(utf8(s))
    +    return utf8(hash.hexdigest())
    +
    +
    +def is_absolute(path: str) -> bool:
    +    return any(path.startswith(x) for x in ["/", "http:", "https:"])
    diff --git a/venv/lib/python3.7/site-packages/tornado/websocket.py b/venv/lib/python3.7/site-packages/tornado/websocket.py
    new file mode 100644
    index 0000000..d991fee
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/websocket.py
    @@ -0,0 +1,1663 @@
    +"""Implementation of the WebSocket protocol.
    +
    +`WebSockets `_ allow for bidirectional
    +communication between the browser and server.
    +
    +WebSockets are supported in the current versions of all major browsers,
    +although older versions that do not support WebSockets are still in use
    +(refer to http://caniuse.com/websockets for details).
    +
    +This module implements the final version of the WebSocket protocol as
    +defined in `RFC 6455 `_.  Certain
    +browser versions (notably Safari 5.x) implemented an earlier draft of
    +the protocol (known as "draft 76") and are not compatible with this module.
    +
    +.. versionchanged:: 4.0
    +   Removed support for the draft 76 protocol version.
    +"""
    +
    +import abc
    +import asyncio
    +import base64
    +import hashlib
    +import os
    +import sys
    +import struct
    +import tornado.escape
    +import tornado.web
    +from urllib.parse import urlparse
    +import zlib
    +
    +from tornado.concurrent import Future, future_set_result_unless_cancelled
    +from tornado.escape import utf8, native_str, to_unicode
    +from tornado import gen, httpclient, httputil
    +from tornado.ioloop import IOLoop, PeriodicCallback
    +from tornado.iostream import StreamClosedError, IOStream
    +from tornado.log import gen_log, app_log
    +from tornado import simple_httpclient
    +from tornado.queues import Queue
    +from tornado.tcpclient import TCPClient
    +from tornado.util import _websocket_mask
    +
    +from typing import (
    +    TYPE_CHECKING,
    +    cast,
    +    Any,
    +    Optional,
    +    Dict,
    +    Union,
    +    List,
    +    Awaitable,
    +    Callable,
    +    Tuple,
    +    Type,
    +)
    +from types import TracebackType
    +
    +if TYPE_CHECKING:
    +    from typing_extensions import Protocol
    +
    +    # The zlib compressor types aren't actually exposed anywhere
    +    # publicly, so declare protocols for the portions we use.
    +    class _Compressor(Protocol):
    +        def compress(self, data: bytes) -> bytes:
    +            pass
    +
    +        def flush(self, mode: int) -> bytes:
    +            pass
    +
    +    class _Decompressor(Protocol):
    +        unconsumed_tail = b""  # type: bytes
    +
    +        def decompress(self, data: bytes, max_length: int) -> bytes:
    +            pass
    +
    +    class _WebSocketDelegate(Protocol):
    +        # The common base interface implemented by WebSocketHandler on
    +        # the server side and WebSocketClientConnection on the client
    +        # side.
    +        def on_ws_connection_close(
    +            self, close_code: int = None, close_reason: str = None
    +        ) -> None:
    +            pass
    +
    +        def on_message(self, message: Union[str, bytes]) -> Optional["Awaitable[None]"]:
    +            pass
    +
    +        def on_ping(self, data: bytes) -> None:
    +            pass
    +
    +        def on_pong(self, data: bytes) -> None:
    +            pass
    +
    +        def log_exception(
    +            self,
    +            typ: Optional[Type[BaseException]],
    +            value: Optional[BaseException],
    +            tb: Optional[TracebackType],
    +        ) -> None:
    +            pass
    +
    +
    +_default_max_message_size = 10 * 1024 * 1024
    +
    +
    +class WebSocketError(Exception):
    +    pass
    +
    +
    +class WebSocketClosedError(WebSocketError):
    +    """Raised by operations on a closed connection.
    +
    +    .. versionadded:: 3.2
    +    """
    +
    +    pass
    +
    +
    +class _DecompressTooLargeError(Exception):
    +    pass
    +
    +
    +class _WebSocketParams(object):
    +    def __init__(
    +        self,
    +        ping_interval: float = None,
    +        ping_timeout: float = None,
    +        max_message_size: int = _default_max_message_size,
    +        compression_options: Dict[str, Any] = None,
    +    ) -> None:
    +        self.ping_interval = ping_interval
    +        self.ping_timeout = ping_timeout
    +        self.max_message_size = max_message_size
    +        self.compression_options = compression_options
    +
    +
    +class WebSocketHandler(tornado.web.RequestHandler):
    +    """Subclass this class to create a basic WebSocket handler.
    +
    +    Override `on_message` to handle incoming messages, and use
    +    `write_message` to send messages to the client. You can also
    +    override `open` and `on_close` to handle opened and closed
    +    connections.
    +
    +    Custom upgrade response headers can be sent by overriding
    +    `~tornado.web.RequestHandler.set_default_headers` or
    +    `~tornado.web.RequestHandler.prepare`.
    +
    +    See http://dev.w3.org/html5/websockets/ for details on the
    +    JavaScript interface.  The protocol is specified at
    +    http://tools.ietf.org/html/rfc6455.
    +
    +    Here is an example WebSocket handler that echos back all received messages
    +    back to the client:
    +
    +    .. testcode::
    +
    +      class EchoWebSocket(tornado.websocket.WebSocketHandler):
    +          def open(self):
    +              print("WebSocket opened")
    +
    +          def on_message(self, message):
    +              self.write_message(u"You said: " + message)
    +
    +          def on_close(self):
    +              print("WebSocket closed")
    +
    +    .. testoutput::
    +       :hide:
    +
    +    WebSockets are not standard HTTP connections. The "handshake" is
    +    HTTP, but after the handshake, the protocol is
    +    message-based. Consequently, most of the Tornado HTTP facilities
    +    are not available in handlers of this type. The only communication
    +    methods available to you are `write_message()`, `ping()`, and
    +    `close()`. Likewise, your request handler class should implement
    +    `open()` method rather than ``get()`` or ``post()``.
    +
    +    If you map the handler above to ``/websocket`` in your application, you can
    +    invoke it in JavaScript with::
    +
    +      var ws = new WebSocket("ws://localhost:8888/websocket");
    +      ws.onopen = function() {
    +         ws.send("Hello, world");
    +      };
    +      ws.onmessage = function (evt) {
    +         alert(evt.data);
    +      };
    +
    +    This script pops up an alert box that says "You said: Hello, world".
    +
    +    Web browsers allow any site to open a websocket connection to any other,
    +    instead of using the same-origin policy that governs other network
    +    access from javascript.  This can be surprising and is a potential
    +    security hole, so since Tornado 4.0 `WebSocketHandler` requires
    +    applications that wish to receive cross-origin websockets to opt in
    +    by overriding the `~WebSocketHandler.check_origin` method (see that
    +    method's docs for details).  Failure to do so is the most likely
    +    cause of 403 errors when making a websocket connection.
    +
    +    When using a secure websocket connection (``wss://``) with a self-signed
    +    certificate, the connection from a browser may fail because it wants
    +    to show the "accept this certificate" dialog but has nowhere to show it.
    +    You must first visit a regular HTML page using the same certificate
    +    to accept it before the websocket connection will succeed.
    +
    +    If the application setting ``websocket_ping_interval`` has a non-zero
    +    value, a ping will be sent periodically, and the connection will be
    +    closed if a response is not received before the ``websocket_ping_timeout``.
    +
    +    Messages larger than the ``websocket_max_message_size`` application setting
    +    (default 10MiB) will not be accepted.
    +
    +    .. versionchanged:: 4.5
    +       Added ``websocket_ping_interval``, ``websocket_ping_timeout``, and
    +       ``websocket_max_message_size``.
    +    """
    +
    +    def __init__(
    +        self,
    +        application: tornado.web.Application,
    +        request: httputil.HTTPServerRequest,
    +        **kwargs: Any
    +    ) -> None:
    +        super(WebSocketHandler, self).__init__(application, request, **kwargs)
    +        self.ws_connection = None  # type: Optional[WebSocketProtocol]
    +        self.close_code = None  # type: Optional[int]
    +        self.close_reason = None  # type: Optional[str]
    +        self.stream = None  # type: Optional[IOStream]
    +        self._on_close_called = False
    +
    +    async def get(self, *args: Any, **kwargs: Any) -> None:
    +        self.open_args = args
    +        self.open_kwargs = kwargs
    +
    +        # Upgrade header should be present and should be equal to WebSocket
    +        if self.request.headers.get("Upgrade", "").lower() != "websocket":
    +            self.set_status(400)
    +            log_msg = 'Can "Upgrade" only to "WebSocket".'
    +            self.finish(log_msg)
    +            gen_log.debug(log_msg)
    +            return
    +
    +        # Connection header should be upgrade.
    +        # Some proxy servers/load balancers
    +        # might mess with it.
    +        headers = self.request.headers
    +        connection = map(
    +            lambda s: s.strip().lower(), headers.get("Connection", "").split(",")
    +        )
    +        if "upgrade" not in connection:
    +            self.set_status(400)
    +            log_msg = '"Connection" must be "Upgrade".'
    +            self.finish(log_msg)
    +            gen_log.debug(log_msg)
    +            return
    +
    +        # Handle WebSocket Origin naming convention differences
    +        # The difference between version 8 and 13 is that in 8 the
    +        # client sends a "Sec-Websocket-Origin" header and in 13 it's
    +        # simply "Origin".
    +        if "Origin" in self.request.headers:
    +            origin = self.request.headers.get("Origin")
    +        else:
    +            origin = self.request.headers.get("Sec-Websocket-Origin", None)
    +
    +        # If there was an origin header, check to make sure it matches
    +        # according to check_origin. When the origin is None, we assume it
    +        # did not come from a browser and that it can be passed on.
    +        if origin is not None and not self.check_origin(origin):
    +            self.set_status(403)
    +            log_msg = "Cross origin websockets not allowed"
    +            self.finish(log_msg)
    +            gen_log.debug(log_msg)
    +            return
    +
    +        self.ws_connection = self.get_websocket_protocol()
    +        if self.ws_connection:
    +            await self.ws_connection.accept_connection(self)
    +        else:
    +            self.set_status(426, "Upgrade Required")
    +            self.set_header("Sec-WebSocket-Version", "7, 8, 13")
    +
    +    stream = None
    +
    +    @property
    +    def ping_interval(self) -> Optional[float]:
    +        """The interval for websocket keep-alive pings.
    +
    +        Set websocket_ping_interval = 0 to disable pings.
    +        """
    +        return self.settings.get("websocket_ping_interval", None)
    +
    +    @property
    +    def ping_timeout(self) -> Optional[float]:
    +        """If no ping is received in this many seconds,
    +        close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).
    +        Default is max of 3 pings or 30 seconds.
    +        """
    +        return self.settings.get("websocket_ping_timeout", None)
    +
    +    @property
    +    def max_message_size(self) -> int:
    +        """Maximum allowed message size.
    +
    +        If the remote peer sends a message larger than this, the connection
    +        will be closed.
    +
    +        Default is 10MiB.
    +        """
    +        return self.settings.get(
    +            "websocket_max_message_size", _default_max_message_size
    +        )
    +
    +    def write_message(
    +        self, message: Union[bytes, str, Dict[str, Any]], binary: bool = False
    +    ) -> "Future[None]":
    +        """Sends the given message to the client of this Web Socket.
    +
    +        The message may be either a string or a dict (which will be
    +        encoded as json).  If the ``binary`` argument is false, the
    +        message will be sent as utf8; in binary mode any byte string
    +        is allowed.
    +
    +        If the connection is already closed, raises `WebSocketClosedError`.
    +        Returns a `.Future` which can be used for flow control.
    +
    +        .. versionchanged:: 3.2
    +           `WebSocketClosedError` was added (previously a closed connection
    +           would raise an `AttributeError`)
    +
    +        .. versionchanged:: 4.3
    +           Returns a `.Future` which can be used for flow control.
    +
    +        .. versionchanged:: 5.0
    +           Consistently raises `WebSocketClosedError`. Previously could
    +           sometimes raise `.StreamClosedError`.
    +        """
    +        if self.ws_connection is None or self.ws_connection.is_closing():
    +            raise WebSocketClosedError()
    +        if isinstance(message, dict):
    +            message = tornado.escape.json_encode(message)
    +        return self.ws_connection.write_message(message, binary=binary)
    +
    +    def select_subprotocol(self, subprotocols: List[str]) -> Optional[str]:
    +        """Override to implement subprotocol negotiation.
    +
    +        ``subprotocols`` is a list of strings identifying the
    +        subprotocols proposed by the client.  This method may be
    +        overridden to return one of those strings to select it, or
    +        ``None`` to not select a subprotocol.
    +
    +        Failure to select a subprotocol does not automatically abort
    +        the connection, although clients may close the connection if
    +        none of their proposed subprotocols was selected.
    +
    +        The list may be empty, in which case this method must return
    +        None. This method is always called exactly once even if no
    +        subprotocols were proposed so that the handler can be advised
    +        of this fact.
    +
    +        .. versionchanged:: 5.1
    +
    +           Previously, this method was called with a list containing
    +           an empty string instead of an empty list if no subprotocols
    +           were proposed by the client.
    +        """
    +        return None
    +
    +    @property
    +    def selected_subprotocol(self) -> Optional[str]:
    +        """The subprotocol returned by `select_subprotocol`.
    +
    +        .. versionadded:: 5.1
    +        """
    +        assert self.ws_connection is not None
    +        return self.ws_connection.selected_subprotocol
    +
    +    def get_compression_options(self) -> Optional[Dict[str, Any]]:
    +        """Override to return compression options for the connection.
    +
    +        If this method returns None (the default), compression will
    +        be disabled.  If it returns a dict (even an empty one), it
    +        will be enabled.  The contents of the dict may be used to
    +        control the following compression options:
    +
    +        ``compression_level`` specifies the compression level.
    +
    +        ``mem_level`` specifies the amount of memory used for the internal compression state.
    +
    +         These parameters are documented in details here:
    +         https://docs.python.org/3.6/library/zlib.html#zlib.compressobj
    +
    +        .. versionadded:: 4.1
    +
    +        .. versionchanged:: 4.5
    +
    +           Added ``compression_level`` and ``mem_level``.
    +        """
    +        # TODO: Add wbits option.
    +        return None
    +
    +    def open(self, *args: str, **kwargs: str) -> Optional[Awaitable[None]]:
    +        """Invoked when a new WebSocket is opened.
    +
    +        The arguments to `open` are extracted from the `tornado.web.URLSpec`
    +        regular expression, just like the arguments to
    +        `tornado.web.RequestHandler.get`.
    +
    +        `open` may be a coroutine. `on_message` will not be called until
    +        `open` has returned.
    +
    +        .. versionchanged:: 5.1
    +
    +           ``open`` may be a coroutine.
    +        """
    +        pass
    +
    +    def on_message(self, message: Union[str, bytes]) -> Optional[Awaitable[None]]:
    +        """Handle incoming messages on the WebSocket
    +
    +        This method must be overridden.
    +
    +        .. versionchanged:: 4.5
    +
    +           ``on_message`` can be a coroutine.
    +        """
    +        raise NotImplementedError
    +
    +    def ping(self, data: Union[str, bytes] = b"") -> None:
    +        """Send ping frame to the remote end.
    +
    +        The data argument allows a small amount of data (up to 125
    +        bytes) to be sent as a part of the ping message. Note that not
    +        all websocket implementations expose this data to
    +        applications.
    +
    +        Consider using the ``websocket_ping_interval`` application
    +        setting instead of sending pings manually.
    +
    +        .. versionchanged:: 5.1
    +
    +           The data argument is now optional.
    +
    +        """
    +        data = utf8(data)
    +        if self.ws_connection is None or self.ws_connection.is_closing():
    +            raise WebSocketClosedError()
    +        self.ws_connection.write_ping(data)
    +
    +    def on_pong(self, data: bytes) -> None:
    +        """Invoked when the response to a ping frame is received."""
    +        pass
    +
    +    def on_ping(self, data: bytes) -> None:
    +        """Invoked when the a ping frame is received."""
    +        pass
    +
    +    def on_close(self) -> None:
    +        """Invoked when the WebSocket is closed.
    +
    +        If the connection was closed cleanly and a status code or reason
    +        phrase was supplied, these values will be available as the attributes
    +        ``self.close_code`` and ``self.close_reason``.
    +
    +        .. versionchanged:: 4.0
    +
    +           Added ``close_code`` and ``close_reason`` attributes.
    +        """
    +        pass
    +
    +    def close(self, code: int = None, reason: str = None) -> None:
    +        """Closes this Web Socket.
    +
    +        Once the close handshake is successful the socket will be closed.
    +
    +        ``code`` may be a numeric status code, taken from the values
    +        defined in `RFC 6455 section 7.4.1
    +        `_.
    +        ``reason`` may be a textual message about why the connection is
    +        closing.  These values are made available to the client, but are
    +        not otherwise interpreted by the websocket protocol.
    +
    +        .. versionchanged:: 4.0
    +
    +           Added the ``code`` and ``reason`` arguments.
    +        """
    +        if self.ws_connection:
    +            self.ws_connection.close(code, reason)
    +            self.ws_connection = None
    +
    +    def check_origin(self, origin: str) -> bool:
    +        """Override to enable support for allowing alternate origins.
    +
    +        The ``origin`` argument is the value of the ``Origin`` HTTP
    +        header, the url responsible for initiating this request.  This
    +        method is not called for clients that do not send this header;
    +        such requests are always allowed (because all browsers that
    +        implement WebSockets support this header, and non-browser
    +        clients do not have the same cross-site security concerns).
    +
    +        Should return ``True`` to accept the request or ``False`` to
    +        reject it. By default, rejects all requests with an origin on
    +        a host other than this one.
    +
    +        This is a security protection against cross site scripting attacks on
    +        browsers, since WebSockets are allowed to bypass the usual same-origin
    +        policies and don't use CORS headers.
    +
    +        .. warning::
    +
    +           This is an important security measure; don't disable it
    +           without understanding the security implications. In
    +           particular, if your authentication is cookie-based, you
    +           must either restrict the origins allowed by
    +           ``check_origin()`` or implement your own XSRF-like
    +           protection for websocket connections. See `these
    +           `_
    +           `articles
    +           `_
    +           for more.
    +
    +        To accept all cross-origin traffic (which was the default prior to
    +        Tornado 4.0), simply override this method to always return ``True``::
    +
    +            def check_origin(self, origin):
    +                return True
    +
    +        To allow connections from any subdomain of your site, you might
    +        do something like::
    +
    +            def check_origin(self, origin):
    +                parsed_origin = urllib.parse.urlparse(origin)
    +                return parsed_origin.netloc.endswith(".mydomain.com")
    +
    +        .. versionadded:: 4.0
    +
    +        """
    +        parsed_origin = urlparse(origin)
    +        origin = parsed_origin.netloc
    +        origin = origin.lower()
    +
    +        host = self.request.headers.get("Host")
    +
    +        # Check to see that origin matches host directly, including ports
    +        return origin == host
    +
    +    def set_nodelay(self, value: bool) -> None:
    +        """Set the no-delay flag for this stream.
    +
    +        By default, small messages may be delayed and/or combined to minimize
    +        the number of packets sent.  This can sometimes cause 200-500ms delays
    +        due to the interaction between Nagle's algorithm and TCP delayed
    +        ACKs.  To reduce this delay (at the expense of possibly increasing
    +        bandwidth usage), call ``self.set_nodelay(True)`` once the websocket
    +        connection is established.
    +
    +        See `.BaseIOStream.set_nodelay` for additional details.
    +
    +        .. versionadded:: 3.1
    +        """
    +        assert self.ws_connection is not None
    +        self.ws_connection.set_nodelay(value)
    +
    +    def on_connection_close(self) -> None:
    +        if self.ws_connection:
    +            self.ws_connection.on_connection_close()
    +            self.ws_connection = None
    +        if not self._on_close_called:
    +            self._on_close_called = True
    +            self.on_close()
    +            self._break_cycles()
    +
    +    def on_ws_connection_close(
    +        self, close_code: int = None, close_reason: str = None
    +    ) -> None:
    +        self.close_code = close_code
    +        self.close_reason = close_reason
    +        self.on_connection_close()
    +
    +    def _break_cycles(self) -> None:
    +        # WebSocketHandlers call finish() early, but we don't want to
    +        # break up reference cycles (which makes it impossible to call
    +        # self.render_string) until after we've really closed the
    +        # connection (if it was established in the first place,
    +        # indicated by status code 101).
    +        if self.get_status() != 101 or self._on_close_called:
    +            super(WebSocketHandler, self)._break_cycles()
    +
    +    def send_error(self, *args: Any, **kwargs: Any) -> None:
    +        if self.stream is None:
    +            super(WebSocketHandler, self).send_error(*args, **kwargs)
    +        else:
    +            # If we get an uncaught exception during the handshake,
    +            # we have no choice but to abruptly close the connection.
    +            # TODO: for uncaught exceptions after the handshake,
    +            # we can close the connection more gracefully.
    +            self.stream.close()
    +
    +    def get_websocket_protocol(self) -> Optional["WebSocketProtocol"]:
    +        websocket_version = self.request.headers.get("Sec-WebSocket-Version")
    +        if websocket_version in ("7", "8", "13"):
    +            params = _WebSocketParams(
    +                ping_interval=self.ping_interval,
    +                ping_timeout=self.ping_timeout,
    +                max_message_size=self.max_message_size,
    +                compression_options=self.get_compression_options(),
    +            )
    +            return WebSocketProtocol13(self, False, params)
    +        return None
    +
    +    def _detach_stream(self) -> IOStream:
    +        # disable non-WS methods
    +        for method in [
    +            "write",
    +            "redirect",
    +            "set_header",
    +            "set_cookie",
    +            "set_status",
    +            "flush",
    +            "finish",
    +        ]:
    +            setattr(self, method, _raise_not_supported_for_websockets)
    +        return self.detach()
    +
    +
    +def _raise_not_supported_for_websockets(*args: Any, **kwargs: Any) -> None:
    +    raise RuntimeError("Method not supported for Web Sockets")
    +
    +
    +class WebSocketProtocol(abc.ABC):
    +    """Base class for WebSocket protocol versions.
    +    """
    +
    +    def __init__(self, handler: "_WebSocketDelegate") -> None:
    +        self.handler = handler
    +        self.stream = None  # type: Optional[IOStream]
    +        self.client_terminated = False
    +        self.server_terminated = False
    +
    +    def _run_callback(
    +        self, callback: Callable, *args: Any, **kwargs: Any
    +    ) -> "Optional[Future[Any]]":
    +        """Runs the given callback with exception handling.
    +
    +        If the callback is a coroutine, returns its Future. On error, aborts the
    +        websocket connection and returns None.
    +        """
    +        try:
    +            result = callback(*args, **kwargs)
    +        except Exception:
    +            self.handler.log_exception(*sys.exc_info())
    +            self._abort()
    +            return None
    +        else:
    +            if result is not None:
    +                result = gen.convert_yielded(result)
    +                assert self.stream is not None
    +                self.stream.io_loop.add_future(result, lambda f: f.result())
    +            return result
    +
    +    def on_connection_close(self) -> None:
    +        self._abort()
    +
    +    def _abort(self) -> None:
    +        """Instantly aborts the WebSocket connection by closing the socket"""
    +        self.client_terminated = True
    +        self.server_terminated = True
    +        if self.stream is not None:
    +            self.stream.close()  # forcibly tear down the connection
    +        self.close()  # let the subclass cleanup
    +
    +    @abc.abstractmethod
    +    def close(self, code: int = None, reason: str = None) -> None:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    def is_closing(self) -> bool:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    async def accept_connection(self, handler: WebSocketHandler) -> None:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    def write_message(
    +        self, message: Union[str, bytes], binary: bool = False
    +    ) -> "Future[None]":
    +        raise NotImplementedError()
    +
    +    @property
    +    @abc.abstractmethod
    +    def selected_subprotocol(self) -> Optional[str]:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    def write_ping(self, data: bytes) -> None:
    +        raise NotImplementedError()
    +
    +    # The entry points below are used by WebSocketClientConnection,
    +    # which was introduced after we only supported a single version of
    +    # WebSocketProtocol. The WebSocketProtocol/WebSocketProtocol13
    +    # boundary is currently pretty ad-hoc.
    +    @abc.abstractmethod
    +    def _process_server_headers(
    +        self, key: Union[str, bytes], headers: httputil.HTTPHeaders
    +    ) -> None:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    def start_pinging(self) -> None:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    async def _receive_frame_loop(self) -> None:
    +        raise NotImplementedError()
    +
    +    @abc.abstractmethod
    +    def set_nodelay(self, x: bool) -> None:
    +        raise NotImplementedError()
    +
    +
    +class _PerMessageDeflateCompressor(object):
    +    def __init__(
    +        self,
    +        persistent: bool,
    +        max_wbits: Optional[int],
    +        compression_options: Dict[str, Any] = None,
    +    ) -> None:
    +        if max_wbits is None:
    +            max_wbits = zlib.MAX_WBITS
    +        # There is no symbolic constant for the minimum wbits value.
    +        if not (8 <= max_wbits <= zlib.MAX_WBITS):
    +            raise ValueError(
    +                "Invalid max_wbits value %r; allowed range 8-%d",
    +                max_wbits,
    +                zlib.MAX_WBITS,
    +            )
    +        self._max_wbits = max_wbits
    +
    +        if (
    +            compression_options is None
    +            or "compression_level" not in compression_options
    +        ):
    +            self._compression_level = tornado.web.GZipContentEncoding.GZIP_LEVEL
    +        else:
    +            self._compression_level = compression_options["compression_level"]
    +
    +        if compression_options is None or "mem_level" not in compression_options:
    +            self._mem_level = 8
    +        else:
    +            self._mem_level = compression_options["mem_level"]
    +
    +        if persistent:
    +            self._compressor = self._create_compressor()  # type: Optional[_Compressor]
    +        else:
    +            self._compressor = None
    +
    +    def _create_compressor(self) -> "_Compressor":
    +        return zlib.compressobj(
    +            self._compression_level, zlib.DEFLATED, -self._max_wbits, self._mem_level
    +        )
    +
    +    def compress(self, data: bytes) -> bytes:
    +        compressor = self._compressor or self._create_compressor()
    +        data = compressor.compress(data) + compressor.flush(zlib.Z_SYNC_FLUSH)
    +        assert data.endswith(b"\x00\x00\xff\xff")
    +        return data[:-4]
    +
    +
    +class _PerMessageDeflateDecompressor(object):
    +    def __init__(
    +        self,
    +        persistent: bool,
    +        max_wbits: Optional[int],
    +        max_message_size: int,
    +        compression_options: Dict[str, Any] = None,
    +    ) -> None:
    +        self._max_message_size = max_message_size
    +        if max_wbits is None:
    +            max_wbits = zlib.MAX_WBITS
    +        if not (8 <= max_wbits <= zlib.MAX_WBITS):
    +            raise ValueError(
    +                "Invalid max_wbits value %r; allowed range 8-%d",
    +                max_wbits,
    +                zlib.MAX_WBITS,
    +            )
    +        self._max_wbits = max_wbits
    +        if persistent:
    +            self._decompressor = (
    +                self._create_decompressor()
    +            )  # type: Optional[_Decompressor]
    +        else:
    +            self._decompressor = None
    +
    +    def _create_decompressor(self) -> "_Decompressor":
    +        return zlib.decompressobj(-self._max_wbits)
    +
    +    def decompress(self, data: bytes) -> bytes:
    +        decompressor = self._decompressor or self._create_decompressor()
    +        result = decompressor.decompress(
    +            data + b"\x00\x00\xff\xff", self._max_message_size
    +        )
    +        if decompressor.unconsumed_tail:
    +            raise _DecompressTooLargeError()
    +        return result
    +
    +
    +class WebSocketProtocol13(WebSocketProtocol):
    +    """Implementation of the WebSocket protocol from RFC 6455.
    +
    +    This class supports versions 7 and 8 of the protocol in addition to the
    +    final version 13.
    +    """
    +
    +    # Bit masks for the first byte of a frame.
    +    FIN = 0x80
    +    RSV1 = 0x40
    +    RSV2 = 0x20
    +    RSV3 = 0x10
    +    RSV_MASK = RSV1 | RSV2 | RSV3
    +    OPCODE_MASK = 0x0F
    +
    +    stream = None  # type: IOStream
    +
    +    def __init__(
    +        self,
    +        handler: "_WebSocketDelegate",
    +        mask_outgoing: bool,
    +        params: _WebSocketParams,
    +    ) -> None:
    +        WebSocketProtocol.__init__(self, handler)
    +        self.mask_outgoing = mask_outgoing
    +        self.params = params
    +        self._final_frame = False
    +        self._frame_opcode = None
    +        self._masked_frame = None
    +        self._frame_mask = None  # type: Optional[bytes]
    +        self._frame_length = None
    +        self._fragmented_message_buffer = None  # type: Optional[bytes]
    +        self._fragmented_message_opcode = None
    +        self._waiting = None  # type: object
    +        self._compression_options = params.compression_options
    +        self._decompressor = None  # type: Optional[_PerMessageDeflateDecompressor]
    +        self._compressor = None  # type: Optional[_PerMessageDeflateCompressor]
    +        self._frame_compressed = None  # type: Optional[bool]
    +        # The total uncompressed size of all messages received or sent.
    +        # Unicode messages are encoded to utf8.
    +        # Only for testing; subject to change.
    +        self._message_bytes_in = 0
    +        self._message_bytes_out = 0
    +        # The total size of all packets received or sent.  Includes
    +        # the effect of compression, frame overhead, and control frames.
    +        self._wire_bytes_in = 0
    +        self._wire_bytes_out = 0
    +        self.ping_callback = None  # type: Optional[PeriodicCallback]
    +        self.last_ping = 0.0
    +        self.last_pong = 0.0
    +        self.close_code = None  # type: Optional[int]
    +        self.close_reason = None  # type: Optional[str]
    +
    +    # Use a property for this to satisfy the abc.
    +    @property
    +    def selected_subprotocol(self) -> Optional[str]:
    +        return self._selected_subprotocol
    +
    +    @selected_subprotocol.setter
    +    def selected_subprotocol(self, value: Optional[str]) -> None:
    +        self._selected_subprotocol = value
    +
    +    async def accept_connection(self, handler: WebSocketHandler) -> None:
    +        try:
    +            self._handle_websocket_headers(handler)
    +        except ValueError:
    +            handler.set_status(400)
    +            log_msg = "Missing/Invalid WebSocket headers"
    +            handler.finish(log_msg)
    +            gen_log.debug(log_msg)
    +            return
    +
    +        try:
    +            await self._accept_connection(handler)
    +        except asyncio.CancelledError:
    +            self._abort()
    +            return
    +        except ValueError:
    +            gen_log.debug("Malformed WebSocket request received", exc_info=True)
    +            self._abort()
    +            return
    +
    +    def _handle_websocket_headers(self, handler: WebSocketHandler) -> None:
    +        """Verifies all invariant- and required headers
    +
    +        If a header is missing or have an incorrect value ValueError will be
    +        raised
    +        """
    +        fields = ("Host", "Sec-Websocket-Key", "Sec-Websocket-Version")
    +        if not all(map(lambda f: handler.request.headers.get(f), fields)):
    +            raise ValueError("Missing/Invalid WebSocket headers")
    +
    +    @staticmethod
    +    def compute_accept_value(key: Union[str, bytes]) -> str:
    +        """Computes the value for the Sec-WebSocket-Accept header,
    +        given the value for Sec-WebSocket-Key.
    +        """
    +        sha1 = hashlib.sha1()
    +        sha1.update(utf8(key))
    +        sha1.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")  # Magic value
    +        return native_str(base64.b64encode(sha1.digest()))
    +
    +    def _challenge_response(self, handler: WebSocketHandler) -> str:
    +        return WebSocketProtocol13.compute_accept_value(
    +            cast(str, handler.request.headers.get("Sec-Websocket-Key"))
    +        )
    +
    +    async def _accept_connection(self, handler: WebSocketHandler) -> None:
    +        subprotocol_header = handler.request.headers.get("Sec-WebSocket-Protocol")
    +        if subprotocol_header:
    +            subprotocols = [s.strip() for s in subprotocol_header.split(",")]
    +        else:
    +            subprotocols = []
    +        self.selected_subprotocol = handler.select_subprotocol(subprotocols)
    +        if self.selected_subprotocol:
    +            assert self.selected_subprotocol in subprotocols
    +            handler.set_header("Sec-WebSocket-Protocol", self.selected_subprotocol)
    +
    +        extensions = self._parse_extensions_header(handler.request.headers)
    +        for ext in extensions:
    +            if ext[0] == "permessage-deflate" and self._compression_options is not None:
    +                # TODO: negotiate parameters if compression_options
    +                # specifies limits.
    +                self._create_compressors("server", ext[1], self._compression_options)
    +                if (
    +                    "client_max_window_bits" in ext[1]
    +                    and ext[1]["client_max_window_bits"] is None
    +                ):
    +                    # Don't echo an offered client_max_window_bits
    +                    # parameter with no value.
    +                    del ext[1]["client_max_window_bits"]
    +                handler.set_header(
    +                    "Sec-WebSocket-Extensions",
    +                    httputil._encode_header("permessage-deflate", ext[1]),
    +                )
    +                break
    +
    +        handler.clear_header("Content-Type")
    +        handler.set_status(101)
    +        handler.set_header("Upgrade", "websocket")
    +        handler.set_header("Connection", "Upgrade")
    +        handler.set_header("Sec-WebSocket-Accept", self._challenge_response(handler))
    +        handler.finish()
    +
    +        self.stream = handler._detach_stream()
    +
    +        self.start_pinging()
    +        try:
    +            open_result = handler.open(*handler.open_args, **handler.open_kwargs)
    +            if open_result is not None:
    +                await open_result
    +        except Exception:
    +            handler.log_exception(*sys.exc_info())
    +            self._abort()
    +            return
    +
    +        await self._receive_frame_loop()
    +
    +    def _parse_extensions_header(
    +        self, headers: httputil.HTTPHeaders
    +    ) -> List[Tuple[str, Dict[str, str]]]:
    +        extensions = headers.get("Sec-WebSocket-Extensions", "")
    +        if extensions:
    +            return [httputil._parse_header(e.strip()) for e in extensions.split(",")]
    +        return []
    +
    +    def _process_server_headers(
    +        self, key: Union[str, bytes], headers: httputil.HTTPHeaders
    +    ) -> None:
    +        """Process the headers sent by the server to this client connection.
    +
    +        'key' is the websocket handshake challenge/response key.
    +        """
    +        assert headers["Upgrade"].lower() == "websocket"
    +        assert headers["Connection"].lower() == "upgrade"
    +        accept = self.compute_accept_value(key)
    +        assert headers["Sec-Websocket-Accept"] == accept
    +
    +        extensions = self._parse_extensions_header(headers)
    +        for ext in extensions:
    +            if ext[0] == "permessage-deflate" and self._compression_options is not None:
    +                self._create_compressors("client", ext[1])
    +            else:
    +                raise ValueError("unsupported extension %r", ext)
    +
    +        self.selected_subprotocol = headers.get("Sec-WebSocket-Protocol", None)
    +
    +    def _get_compressor_options(
    +        self,
    +        side: str,
    +        agreed_parameters: Dict[str, Any],
    +        compression_options: Dict[str, Any] = None,
    +    ) -> Dict[str, Any]:
    +        """Converts a websocket agreed_parameters set to keyword arguments
    +        for our compressor objects.
    +        """
    +        options = dict(
    +            persistent=(side + "_no_context_takeover") not in agreed_parameters
    +        )  # type: Dict[str, Any]
    +        wbits_header = agreed_parameters.get(side + "_max_window_bits", None)
    +        if wbits_header is None:
    +            options["max_wbits"] = zlib.MAX_WBITS
    +        else:
    +            options["max_wbits"] = int(wbits_header)
    +        options["compression_options"] = compression_options
    +        return options
    +
    +    def _create_compressors(
    +        self,
    +        side: str,
    +        agreed_parameters: Dict[str, Any],
    +        compression_options: Dict[str, Any] = None,
    +    ) -> None:
    +        # TODO: handle invalid parameters gracefully
    +        allowed_keys = set(
    +            [
    +                "server_no_context_takeover",
    +                "client_no_context_takeover",
    +                "server_max_window_bits",
    +                "client_max_window_bits",
    +            ]
    +        )
    +        for key in agreed_parameters:
    +            if key not in allowed_keys:
    +                raise ValueError("unsupported compression parameter %r" % key)
    +        other_side = "client" if (side == "server") else "server"
    +        self._compressor = _PerMessageDeflateCompressor(
    +            **self._get_compressor_options(side, agreed_parameters, compression_options)
    +        )
    +        self._decompressor = _PerMessageDeflateDecompressor(
    +            max_message_size=self.params.max_message_size,
    +            **self._get_compressor_options(
    +                other_side, agreed_parameters, compression_options
    +            )
    +        )
    +
    +    def _write_frame(
    +        self, fin: bool, opcode: int, data: bytes, flags: int = 0
    +    ) -> "Future[None]":
    +        data_len = len(data)
    +        if opcode & 0x8:
    +            # All control frames MUST have a payload length of 125
    +            # bytes or less and MUST NOT be fragmented.
    +            if not fin:
    +                raise ValueError("control frames may not be fragmented")
    +            if data_len > 125:
    +                raise ValueError("control frame payloads may not exceed 125 bytes")
    +        if fin:
    +            finbit = self.FIN
    +        else:
    +            finbit = 0
    +        frame = struct.pack("B", finbit | opcode | flags)
    +        if self.mask_outgoing:
    +            mask_bit = 0x80
    +        else:
    +            mask_bit = 0
    +        if data_len < 126:
    +            frame += struct.pack("B", data_len | mask_bit)
    +        elif data_len <= 0xFFFF:
    +            frame += struct.pack("!BH", 126 | mask_bit, data_len)
    +        else:
    +            frame += struct.pack("!BQ", 127 | mask_bit, data_len)
    +        if self.mask_outgoing:
    +            mask = os.urandom(4)
    +            data = mask + _websocket_mask(mask, data)
    +        frame += data
    +        self._wire_bytes_out += len(frame)
    +        return self.stream.write(frame)
    +
    +    def write_message(
    +        self, message: Union[str, bytes], binary: bool = False
    +    ) -> "Future[None]":
    +        """Sends the given message to the client of this Web Socket."""
    +        if binary:
    +            opcode = 0x2
    +        else:
    +            opcode = 0x1
    +        message = tornado.escape.utf8(message)
    +        assert isinstance(message, bytes)
    +        self._message_bytes_out += len(message)
    +        flags = 0
    +        if self._compressor:
    +            message = self._compressor.compress(message)
    +            flags |= self.RSV1
    +        # For historical reasons, write methods in Tornado operate in a semi-synchronous
    +        # mode in which awaiting the Future they return is optional (But errors can
    +        # still be raised). This requires us to go through an awkward dance here
    +        # to transform the errors that may be returned while presenting the same
    +        # semi-synchronous interface.
    +        try:
    +            fut = self._write_frame(True, opcode, message, flags=flags)
    +        except StreamClosedError:
    +            raise WebSocketClosedError()
    +
    +        async def wrapper() -> None:
    +            try:
    +                await fut
    +            except StreamClosedError:
    +                raise WebSocketClosedError()
    +
    +        return asyncio.ensure_future(wrapper())
    +
    +    def write_ping(self, data: bytes) -> None:
    +        """Send ping frame."""
    +        assert isinstance(data, bytes)
    +        self._write_frame(True, 0x9, data)
    +
    +    async def _receive_frame_loop(self) -> None:
    +        try:
    +            while not self.client_terminated:
    +                await self._receive_frame()
    +        except StreamClosedError:
    +            self._abort()
    +        self.handler.on_ws_connection_close(self.close_code, self.close_reason)
    +
    +    async def _read_bytes(self, n: int) -> bytes:
    +        data = await self.stream.read_bytes(n)
    +        self._wire_bytes_in += n
    +        return data
    +
    +    async def _receive_frame(self) -> None:
    +        # Read the frame header.
    +        data = await self._read_bytes(2)
    +        header, mask_payloadlen = struct.unpack("BB", data)
    +        is_final_frame = header & self.FIN
    +        reserved_bits = header & self.RSV_MASK
    +        opcode = header & self.OPCODE_MASK
    +        opcode_is_control = opcode & 0x8
    +        if self._decompressor is not None and opcode != 0:
    +            # Compression flag is present in the first frame's header,
    +            # but we can't decompress until we have all the frames of
    +            # the message.
    +            self._frame_compressed = bool(reserved_bits & self.RSV1)
    +            reserved_bits &= ~self.RSV1
    +        if reserved_bits:
    +            # client is using as-yet-undefined extensions; abort
    +            self._abort()
    +            return
    +        is_masked = bool(mask_payloadlen & 0x80)
    +        payloadlen = mask_payloadlen & 0x7F
    +
    +        # Parse and validate the length.
    +        if opcode_is_control and payloadlen >= 126:
    +            # control frames must have payload < 126
    +            self._abort()
    +            return
    +        if payloadlen < 126:
    +            self._frame_length = payloadlen
    +        elif payloadlen == 126:
    +            data = await self._read_bytes(2)
    +            payloadlen = struct.unpack("!H", data)[0]
    +        elif payloadlen == 127:
    +            data = await self._read_bytes(8)
    +            payloadlen = struct.unpack("!Q", data)[0]
    +        new_len = payloadlen
    +        if self._fragmented_message_buffer is not None:
    +            new_len += len(self._fragmented_message_buffer)
    +        if new_len > self.params.max_message_size:
    +            self.close(1009, "message too big")
    +            self._abort()
    +            return
    +
    +        # Read the payload, unmasking if necessary.
    +        if is_masked:
    +            self._frame_mask = await self._read_bytes(4)
    +        data = await self._read_bytes(payloadlen)
    +        if is_masked:
    +            assert self._frame_mask is not None
    +            data = _websocket_mask(self._frame_mask, data)
    +
    +        # Decide what to do with this frame.
    +        if opcode_is_control:
    +            # control frames may be interleaved with a series of fragmented
    +            # data frames, so control frames must not interact with
    +            # self._fragmented_*
    +            if not is_final_frame:
    +                # control frames must not be fragmented
    +                self._abort()
    +                return
    +        elif opcode == 0:  # continuation frame
    +            if self._fragmented_message_buffer is None:
    +                # nothing to continue
    +                self._abort()
    +                return
    +            self._fragmented_message_buffer += data
    +            if is_final_frame:
    +                opcode = self._fragmented_message_opcode
    +                data = self._fragmented_message_buffer
    +                self._fragmented_message_buffer = None
    +        else:  # start of new data message
    +            if self._fragmented_message_buffer is not None:
    +                # can't start new message until the old one is finished
    +                self._abort()
    +                return
    +            if not is_final_frame:
    +                self._fragmented_message_opcode = opcode
    +                self._fragmented_message_buffer = data
    +
    +        if is_final_frame:
    +            handled_future = self._handle_message(opcode, data)
    +            if handled_future is not None:
    +                await handled_future
    +
    +    def _handle_message(self, opcode: int, data: bytes) -> "Optional[Future[None]]":
    +        """Execute on_message, returning its Future if it is a coroutine."""
    +        if self.client_terminated:
    +            return None
    +
    +        if self._frame_compressed:
    +            assert self._decompressor is not None
    +            try:
    +                data = self._decompressor.decompress(data)
    +            except _DecompressTooLargeError:
    +                self.close(1009, "message too big after decompression")
    +                self._abort()
    +                return None
    +
    +        if opcode == 0x1:
    +            # UTF-8 data
    +            self._message_bytes_in += len(data)
    +            try:
    +                decoded = data.decode("utf-8")
    +            except UnicodeDecodeError:
    +                self._abort()
    +                return None
    +            return self._run_callback(self.handler.on_message, decoded)
    +        elif opcode == 0x2:
    +            # Binary data
    +            self._message_bytes_in += len(data)
    +            return self._run_callback(self.handler.on_message, data)
    +        elif opcode == 0x8:
    +            # Close
    +            self.client_terminated = True
    +            if len(data) >= 2:
    +                self.close_code = struct.unpack(">H", data[:2])[0]
    +            if len(data) > 2:
    +                self.close_reason = to_unicode(data[2:])
    +            # Echo the received close code, if any (RFC 6455 section 5.5.1).
    +            self.close(self.close_code)
    +        elif opcode == 0x9:
    +            # Ping
    +            try:
    +                self._write_frame(True, 0xA, data)
    +            except StreamClosedError:
    +                self._abort()
    +            self._run_callback(self.handler.on_ping, data)
    +        elif opcode == 0xA:
    +            # Pong
    +            self.last_pong = IOLoop.current().time()
    +            return self._run_callback(self.handler.on_pong, data)
    +        else:
    +            self._abort()
    +        return None
    +
    +    def close(self, code: int = None, reason: str = None) -> None:
    +        """Closes the WebSocket connection."""
    +        if not self.server_terminated:
    +            if not self.stream.closed():
    +                if code is None and reason is not None:
    +                    code = 1000  # "normal closure" status code
    +                if code is None:
    +                    close_data = b""
    +                else:
    +                    close_data = struct.pack(">H", code)
    +                if reason is not None:
    +                    close_data += utf8(reason)
    +                try:
    +                    self._write_frame(True, 0x8, close_data)
    +                except StreamClosedError:
    +                    self._abort()
    +            self.server_terminated = True
    +        if self.client_terminated:
    +            if self._waiting is not None:
    +                self.stream.io_loop.remove_timeout(self._waiting)
    +                self._waiting = None
    +            self.stream.close()
    +        elif self._waiting is None:
    +            # Give the client a few seconds to complete a clean shutdown,
    +            # otherwise just close the connection.
    +            self._waiting = self.stream.io_loop.add_timeout(
    +                self.stream.io_loop.time() + 5, self._abort
    +            )
    +
    +    def is_closing(self) -> bool:
    +        """Return ``True`` if this connection is closing.
    +
    +        The connection is considered closing if either side has
    +        initiated its closing handshake or if the stream has been
    +        shut down uncleanly.
    +        """
    +        return self.stream.closed() or self.client_terminated or self.server_terminated
    +
    +    @property
    +    def ping_interval(self) -> Optional[float]:
    +        interval = self.params.ping_interval
    +        if interval is not None:
    +            return interval
    +        return 0
    +
    +    @property
    +    def ping_timeout(self) -> Optional[float]:
    +        timeout = self.params.ping_timeout
    +        if timeout is not None:
    +            return timeout
    +        assert self.ping_interval is not None
    +        return max(3 * self.ping_interval, 30)
    +
    +    def start_pinging(self) -> None:
    +        """Start sending periodic pings to keep the connection alive"""
    +        assert self.ping_interval is not None
    +        if self.ping_interval > 0:
    +            self.last_ping = self.last_pong = IOLoop.current().time()
    +            self.ping_callback = PeriodicCallback(
    +                self.periodic_ping, self.ping_interval * 1000
    +            )
    +            self.ping_callback.start()
    +
    +    def periodic_ping(self) -> None:
    +        """Send a ping to keep the websocket alive
    +
    +        Called periodically if the websocket_ping_interval is set and non-zero.
    +        """
    +        if self.is_closing() and self.ping_callback is not None:
    +            self.ping_callback.stop()
    +            return
    +
    +        # Check for timeout on pong. Make sure that we really have
    +        # sent a recent ping in case the machine with both server and
    +        # client has been suspended since the last ping.
    +        now = IOLoop.current().time()
    +        since_last_pong = now - self.last_pong
    +        since_last_ping = now - self.last_ping
    +        assert self.ping_interval is not None
    +        assert self.ping_timeout is not None
    +        if (
    +            since_last_ping < 2 * self.ping_interval
    +            and since_last_pong > self.ping_timeout
    +        ):
    +            self.close()
    +            return
    +
    +        self.write_ping(b"")
    +        self.last_ping = now
    +
    +    def set_nodelay(self, x: bool) -> None:
    +        self.stream.set_nodelay(x)
    +
    +
    +class WebSocketClientConnection(simple_httpclient._HTTPConnection):
    +    """WebSocket client connection.
    +
    +    This class should not be instantiated directly; use the
    +    `websocket_connect` function instead.
    +    """
    +
    +    protocol = None  # type: WebSocketProtocol
    +
    +    def __init__(
    +        self,
    +        request: httpclient.HTTPRequest,
    +        on_message_callback: Callable[[Union[None, str, bytes]], None] = None,
    +        compression_options: Dict[str, Any] = None,
    +        ping_interval: float = None,
    +        ping_timeout: float = None,
    +        max_message_size: int = _default_max_message_size,
    +        subprotocols: Optional[List[str]] = [],
    +    ) -> None:
    +        self.connect_future = Future()  # type: Future[WebSocketClientConnection]
    +        self.read_queue = Queue(1)  # type: Queue[Union[None, str, bytes]]
    +        self.key = base64.b64encode(os.urandom(16))
    +        self._on_message_callback = on_message_callback
    +        self.close_code = None  # type: Optional[int]
    +        self.close_reason = None  # type: Optional[str]
    +        self.params = _WebSocketParams(
    +            ping_interval=ping_interval,
    +            ping_timeout=ping_timeout,
    +            max_message_size=max_message_size,
    +            compression_options=compression_options,
    +        )
    +
    +        scheme, sep, rest = request.url.partition(":")
    +        scheme = {"ws": "http", "wss": "https"}[scheme]
    +        request.url = scheme + sep + rest
    +        request.headers.update(
    +            {
    +                "Upgrade": "websocket",
    +                "Connection": "Upgrade",
    +                "Sec-WebSocket-Key": self.key,
    +                "Sec-WebSocket-Version": "13",
    +            }
    +        )
    +        if subprotocols is not None:
    +            request.headers["Sec-WebSocket-Protocol"] = ",".join(subprotocols)
    +        if compression_options is not None:
    +            # Always offer to let the server set our max_wbits (and even though
    +            # we don't offer it, we will accept a client_no_context_takeover
    +            # from the server).
    +            # TODO: set server parameters for deflate extension
    +            # if requested in self.compression_options.
    +            request.headers[
    +                "Sec-WebSocket-Extensions"
    +            ] = "permessage-deflate; client_max_window_bits"
    +
    +        self.tcp_client = TCPClient()
    +        super(WebSocketClientConnection, self).__init__(
    +            None,
    +            request,
    +            lambda: None,
    +            self._on_http_response,
    +            104857600,
    +            self.tcp_client,
    +            65536,
    +            104857600,
    +        )
    +
    +    def close(self, code: int = None, reason: str = None) -> None:
    +        """Closes the websocket connection.
    +
    +        ``code`` and ``reason`` are documented under
    +        `WebSocketHandler.close`.
    +
    +        .. versionadded:: 3.2
    +
    +        .. versionchanged:: 4.0
    +
    +           Added the ``code`` and ``reason`` arguments.
    +        """
    +        if self.protocol is not None:
    +            self.protocol.close(code, reason)
    +            self.protocol = None  # type: ignore
    +
    +    def on_connection_close(self) -> None:
    +        if not self.connect_future.done():
    +            self.connect_future.set_exception(StreamClosedError())
    +        self._on_message(None)
    +        self.tcp_client.close()
    +        super(WebSocketClientConnection, self).on_connection_close()
    +
    +    def on_ws_connection_close(
    +        self, close_code: int = None, close_reason: str = None
    +    ) -> None:
    +        self.close_code = close_code
    +        self.close_reason = close_reason
    +        self.on_connection_close()
    +
    +    def _on_http_response(self, response: httpclient.HTTPResponse) -> None:
    +        if not self.connect_future.done():
    +            if response.error:
    +                self.connect_future.set_exception(response.error)
    +            else:
    +                self.connect_future.set_exception(
    +                    WebSocketError("Non-websocket response")
    +                )
    +
    +    async def headers_received(
    +        self,
    +        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
    +        headers: httputil.HTTPHeaders,
    +    ) -> None:
    +        assert isinstance(start_line, httputil.ResponseStartLine)
    +        if start_line.code != 101:
    +            await super(WebSocketClientConnection, self).headers_received(
    +                start_line, headers
    +            )
    +            return
    +
    +        if self._timeout is not None:
    +            self.io_loop.remove_timeout(self._timeout)
    +            self._timeout = None
    +
    +        self.headers = headers
    +        self.protocol = self.get_websocket_protocol()
    +        self.protocol._process_server_headers(self.key, self.headers)
    +        self.protocol.stream = self.connection.detach()
    +
    +        IOLoop.current().add_callback(self.protocol._receive_frame_loop)
    +        self.protocol.start_pinging()
    +
    +        # Once we've taken over the connection, clear the final callback
    +        # we set on the http request.  This deactivates the error handling
    +        # in simple_httpclient that would otherwise interfere with our
    +        # ability to see exceptions.
    +        self.final_callback = None  # type: ignore
    +
    +        future_set_result_unless_cancelled(self.connect_future, self)
    +
    +    def write_message(
    +        self, message: Union[str, bytes], binary: bool = False
    +    ) -> "Future[None]":
    +        """Sends a message to the WebSocket server.
    +
    +        If the stream is closed, raises `WebSocketClosedError`.
    +        Returns a `.Future` which can be used for flow control.
    +
    +        .. versionchanged:: 5.0
    +           Exception raised on a closed stream changed from `.StreamClosedError`
    +           to `WebSocketClosedError`.
    +        """
    +        return self.protocol.write_message(message, binary=binary)
    +
    +    def read_message(
    +        self, callback: Callable[["Future[Union[None, str, bytes]]"], None] = None
    +    ) -> Awaitable[Union[None, str, bytes]]:
    +        """Reads a message from the WebSocket server.
    +
    +        If on_message_callback was specified at WebSocket
    +        initialization, this function will never return messages
    +
    +        Returns a future whose result is the message, or None
    +        if the connection is closed.  If a callback argument
    +        is given it will be called with the future when it is
    +        ready.
    +        """
    +
    +        awaitable = self.read_queue.get()
    +        if callback is not None:
    +            self.io_loop.add_future(asyncio.ensure_future(awaitable), callback)
    +        return awaitable
    +
    +    def on_message(self, message: Union[str, bytes]) -> Optional[Awaitable[None]]:
    +        return self._on_message(message)
    +
    +    def _on_message(
    +        self, message: Union[None, str, bytes]
    +    ) -> Optional[Awaitable[None]]:
    +        if self._on_message_callback:
    +            self._on_message_callback(message)
    +            return None
    +        else:
    +            return self.read_queue.put(message)
    +
    +    def ping(self, data: bytes = b"") -> None:
    +        """Send ping frame to the remote end.
    +
    +        The data argument allows a small amount of data (up to 125
    +        bytes) to be sent as a part of the ping message. Note that not
    +        all websocket implementations expose this data to
    +        applications.
    +
    +        Consider using the ``ping_interval`` argument to
    +        `websocket_connect` instead of sending pings manually.
    +
    +        .. versionadded:: 5.1
    +
    +        """
    +        data = utf8(data)
    +        if self.protocol is None:
    +            raise WebSocketClosedError()
    +        self.protocol.write_ping(data)
    +
    +    def on_pong(self, data: bytes) -> None:
    +        pass
    +
    +    def on_ping(self, data: bytes) -> None:
    +        pass
    +
    +    def get_websocket_protocol(self) -> WebSocketProtocol:
    +        return WebSocketProtocol13(self, mask_outgoing=True, params=self.params)
    +
    +    @property
    +    def selected_subprotocol(self) -> Optional[str]:
    +        """The subprotocol selected by the server.
    +
    +        .. versionadded:: 5.1
    +        """
    +        return self.protocol.selected_subprotocol
    +
    +    def log_exception(
    +        self,
    +        typ: "Optional[Type[BaseException]]",
    +        value: Optional[BaseException],
    +        tb: Optional[TracebackType],
    +    ) -> None:
    +        assert typ is not None
    +        assert value is not None
    +        app_log.error("Uncaught exception %s", value, exc_info=(typ, value, tb))
    +
    +
    +def websocket_connect(
    +    url: Union[str, httpclient.HTTPRequest],
    +    callback: Callable[["Future[WebSocketClientConnection]"], None] = None,
    +    connect_timeout: float = None,
    +    on_message_callback: Callable[[Union[None, str, bytes]], None] = None,
    +    compression_options: Dict[str, Any] = None,
    +    ping_interval: float = None,
    +    ping_timeout: float = None,
    +    max_message_size: int = _default_max_message_size,
    +    subprotocols: List[str] = None,
    +) -> "Awaitable[WebSocketClientConnection]":
    +    """Client-side websocket support.
    +
    +    Takes a url and returns a Future whose result is a
    +    `WebSocketClientConnection`.
    +
    +    ``compression_options`` is interpreted in the same way as the
    +    return value of `.WebSocketHandler.get_compression_options`.
    +
    +    The connection supports two styles of operation. In the coroutine
    +    style, the application typically calls
    +    `~.WebSocketClientConnection.read_message` in a loop::
    +
    +        conn = yield websocket_connect(url)
    +        while True:
    +            msg = yield conn.read_message()
    +            if msg is None: break
    +            # Do something with msg
    +
    +    In the callback style, pass an ``on_message_callback`` to
    +    ``websocket_connect``. In both styles, a message of ``None``
    +    indicates that the connection has been closed.
    +
    +    ``subprotocols`` may be a list of strings specifying proposed
    +    subprotocols. The selected protocol may be found on the
    +    ``selected_subprotocol`` attribute of the connection object
    +    when the connection is complete.
    +
    +    .. versionchanged:: 3.2
    +       Also accepts ``HTTPRequest`` objects in place of urls.
    +
    +    .. versionchanged:: 4.1
    +       Added ``compression_options`` and ``on_message_callback``.
    +
    +    .. versionchanged:: 4.5
    +       Added the ``ping_interval``, ``ping_timeout``, and ``max_message_size``
    +       arguments, which have the same meaning as in `WebSocketHandler`.
    +
    +    .. versionchanged:: 5.0
    +       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
    +
    +    .. versionchanged:: 5.1
    +       Added the ``subprotocols`` argument.
    +    """
    +    if isinstance(url, httpclient.HTTPRequest):
    +        assert connect_timeout is None
    +        request = url
    +        # Copy and convert the headers dict/object (see comments in
    +        # AsyncHTTPClient.fetch)
    +        request.headers = httputil.HTTPHeaders(request.headers)
    +    else:
    +        request = httpclient.HTTPRequest(url, connect_timeout=connect_timeout)
    +    request = cast(
    +        httpclient.HTTPRequest,
    +        httpclient._RequestProxy(request, httpclient.HTTPRequest._DEFAULTS),
    +    )
    +    conn = WebSocketClientConnection(
    +        request,
    +        on_message_callback=on_message_callback,
    +        compression_options=compression_options,
    +        ping_interval=ping_interval,
    +        ping_timeout=ping_timeout,
    +        max_message_size=max_message_size,
    +        subprotocols=subprotocols,
    +    )
    +    if callback is not None:
    +        IOLoop.current().add_future(conn.connect_future, callback)
    +    return conn.connect_future
    diff --git a/venv/lib/python3.7/site-packages/tornado/wsgi.py b/venv/lib/python3.7/site-packages/tornado/wsgi.py
    new file mode 100644
    index 0000000..77124aa
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/tornado/wsgi.py
    @@ -0,0 +1,199 @@
    +#
    +# Copyright 2009 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.
    +
    +"""WSGI support for the Tornado web framework.
    +
    +WSGI is the Python standard for web servers, and allows for interoperability
    +between Tornado and other Python web frameworks and servers.
    +
    +This module provides WSGI support via the `WSGIContainer` class, which
    +makes it possible to run applications using other WSGI frameworks on
    +the Tornado HTTP server. The reverse is not supported; the Tornado
    +`.Application` and `.RequestHandler` classes are designed for use with
    +the Tornado `.HTTPServer` and cannot be used in a generic WSGI
    +container.
    +
    +"""
    +
    +import sys
    +from io import BytesIO
    +import tornado
    +
    +from tornado import escape
    +from tornado import httputil
    +from tornado.log import access_log
    +
    +from typing import List, Tuple, Optional, Callable, Any, Dict, Text
    +from types import TracebackType
    +import typing
    +
    +if typing.TYPE_CHECKING:
    +    from typing import Type  # noqa: F401
    +    from wsgiref.types import WSGIApplication as WSGIAppType  # noqa: F401
    +
    +
    +# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
    +# that are smuggled inside objects of type unicode (via the latin1 encoding).
    +# This function is like those in the tornado.escape module, but defined
    +# here to minimize the temptation to use it in non-wsgi contexts.
    +def to_wsgi_str(s: bytes) -> str:
    +    assert isinstance(s, bytes)
    +    return s.decode("latin1")
    +
    +
    +class WSGIContainer(object):
    +    r"""Makes a WSGI-compatible function runnable on Tornado's HTTP server.
    +
    +    .. warning::
    +
    +       WSGI is a *synchronous* interface, while Tornado's concurrency model
    +       is based on single-threaded asynchronous execution.  This means that
    +       running a WSGI app with Tornado's `WSGIContainer` is *less scalable*
    +       than running the same app in a multi-threaded WSGI server like
    +       ``gunicorn`` or ``uwsgi``.  Use `WSGIContainer` only when there are
    +       benefits to combining Tornado and WSGI in the same process that
    +       outweigh the reduced scalability.
    +
    +    Wrap a WSGI function in a `WSGIContainer` and pass it to `.HTTPServer` to
    +    run it. For example::
    +
    +        def simple_app(environ, start_response):
    +            status = "200 OK"
    +            response_headers = [("Content-type", "text/plain")]
    +            start_response(status, response_headers)
    +            return ["Hello world!\n"]
    +
    +        container = tornado.wsgi.WSGIContainer(simple_app)
    +        http_server = tornado.httpserver.HTTPServer(container)
    +        http_server.listen(8888)
    +        tornado.ioloop.IOLoop.current().start()
    +
    +    This class is intended to let other frameworks (Django, web.py, etc)
    +    run on the Tornado HTTP server and I/O loop.
    +
    +    The `tornado.web.FallbackHandler` class is often useful for mixing
    +    Tornado and WSGI apps in the same server.  See
    +    https://github.com/bdarnell/django-tornado-demo for a complete example.
    +    """
    +
    +    def __init__(self, wsgi_application: "WSGIAppType") -> None:
    +        self.wsgi_application = wsgi_application
    +
    +    def __call__(self, request: httputil.HTTPServerRequest) -> None:
    +        data = {}  # type: Dict[str, Any]
    +        response = []  # type: List[bytes]
    +
    +        def start_response(
    +            status: str,
    +            headers: List[Tuple[str, str]],
    +            exc_info: Optional[
    +                Tuple[
    +                    "Optional[Type[BaseException]]",
    +                    Optional[BaseException],
    +                    Optional[TracebackType],
    +                ]
    +            ] = None,
    +        ) -> Callable[[bytes], Any]:
    +            data["status"] = status
    +            data["headers"] = headers
    +            return response.append
    +
    +        app_response = self.wsgi_application(
    +            WSGIContainer.environ(request), start_response
    +        )
    +        try:
    +            response.extend(app_response)
    +            body = b"".join(response)
    +        finally:
    +            if hasattr(app_response, "close"):
    +                app_response.close()  # type: ignore
    +        if not data:
    +            raise Exception("WSGI app did not call start_response")
    +
    +        status_code_str, reason = data["status"].split(" ", 1)
    +        status_code = int(status_code_str)
    +        headers = data["headers"]  # type: List[Tuple[str, str]]
    +        header_set = set(k.lower() for (k, v) in headers)
    +        body = escape.utf8(body)
    +        if status_code != 304:
    +            if "content-length" not in header_set:
    +                headers.append(("Content-Length", str(len(body))))
    +            if "content-type" not in header_set:
    +                headers.append(("Content-Type", "text/html; charset=UTF-8"))
    +        if "server" not in header_set:
    +            headers.append(("Server", "TornadoServer/%s" % tornado.version))
    +
    +        start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
    +        header_obj = httputil.HTTPHeaders()
    +        for key, value in headers:
    +            header_obj.add(key, value)
    +        assert request.connection is not None
    +        request.connection.write_headers(start_line, header_obj, chunk=body)
    +        request.connection.finish()
    +        self._log(status_code, request)
    +
    +    @staticmethod
    +    def environ(request: httputil.HTTPServerRequest) -> Dict[Text, Any]:
    +        """Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment.
    +        """
    +        hostport = request.host.split(":")
    +        if len(hostport) == 2:
    +            host = hostport[0]
    +            port = int(hostport[1])
    +        else:
    +            host = request.host
    +            port = 443 if request.protocol == "https" else 80
    +        environ = {
    +            "REQUEST_METHOD": request.method,
    +            "SCRIPT_NAME": "",
    +            "PATH_INFO": to_wsgi_str(
    +                escape.url_unescape(request.path, encoding=None, plus=False)
    +            ),
    +            "QUERY_STRING": request.query,
    +            "REMOTE_ADDR": request.remote_ip,
    +            "SERVER_NAME": host,
    +            "SERVER_PORT": str(port),
    +            "SERVER_PROTOCOL": request.version,
    +            "wsgi.version": (1, 0),
    +            "wsgi.url_scheme": request.protocol,
    +            "wsgi.input": BytesIO(escape.utf8(request.body)),
    +            "wsgi.errors": sys.stderr,
    +            "wsgi.multithread": False,
    +            "wsgi.multiprocess": True,
    +            "wsgi.run_once": False,
    +        }
    +        if "Content-Type" in request.headers:
    +            environ["CONTENT_TYPE"] = request.headers.pop("Content-Type")
    +        if "Content-Length" in request.headers:
    +            environ["CONTENT_LENGTH"] = request.headers.pop("Content-Length")
    +        for key, value in request.headers.items():
    +            environ["HTTP_" + key.replace("-", "_").upper()] = value
    +        return environ
    +
    +    def _log(self, status_code: int, request: httputil.HTTPServerRequest) -> None:
    +        if status_code < 400:
    +            log_method = access_log.info
    +        elif status_code < 500:
    +            log_method = access_log.warning
    +        else:
    +            log_method = access_log.error
    +        request_time = 1000.0 * request.request_time()
    +        assert request.method is not None
    +        assert request.uri is not None
    +        summary = request.method + " " + request.uri + " (" + request.remote_ip + ")"
    +        log_method("%d %s %.2fms", status_code, summary, request_time)
    +
    +
    +HTTPRequest = httputil.HTTPServerRequest
    diff --git a/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/INSTALLER
    new file mode 100644
    index 0000000..a1b589e
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/INSTALLER
    @@ -0,0 +1 @@
    +pip
    diff --git a/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/LICENSE b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/LICENSE
    new file mode 100644
    index 0000000..7da84f4
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/LICENSE
    @@ -0,0 +1,20 @@
    +The MIT License (MIT)
    +
    +Copyright (c) 2014-2019 Thomas Kemmer
    +
    +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/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/METADATA b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/METADATA
    new file mode 100644
    index 0000000..18b83ff
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/METADATA
    @@ -0,0 +1,124 @@
    +Metadata-Version: 2.1
    +Name: uritools
    +Version: 3.0.0
    +Summary: URI parsing, classification and composition
    +Home-page: https://github.com/tkem/uritools/
    +Author: Thomas Kemmer
    +Author-email: tkemmer@computer.org
    +License: MIT
    +Platform: UNKNOWN
    +Classifier: Development Status :: 5 - Production/Stable
    +Classifier: Environment :: Other Environment
    +Classifier: Intended Audience :: Developers
    +Classifier: License :: OSI Approved :: MIT License
    +Classifier: Operating System :: OS Independent
    +Classifier: Programming Language :: Python
    +Classifier: Programming Language :: Python :: 3
    +Classifier: Programming Language :: Python :: 3.5
    +Classifier: Programming Language :: Python :: 3.6
    +Classifier: Programming Language :: Python :: 3.7
    +Classifier: Programming Language :: Python :: 3.8
    +Classifier: Topic :: Software Development :: Libraries :: Python Modules
    +Requires-Python: ~=3.5
    +
    +uritools
    +========================================================================
    +
    +.. image:: https://img.shields.io/pypi/v/uritools
    +    :target: https://pypi.org/project/uritools
    +    :alt: Latest PyPI version
    +
    +.. image:: https://img.shields.io/readthedocs/uritools
    +   :target: https://uritools.readthedocs.io
    +   :alt: Documentation build status
    +
    +.. image:: https://img.shields.io/travis/tkem/uritools
    +    :target: https://travis-ci.org/tkem/uritools
    +    :alt: Travis CI build status
    +
    +.. image:: https://img.shields.io/coveralls/tkem/uritools
    +   :target: https://coveralls.io/r/tkem/uritools
    +   :alt: Test coverage
    +
    +.. image:: https://img.shields.io/github/license/tkem/uritools
    +   :target: http://raw.github.com/tkem/uritools/master/LICENSE
    +   :alt: License
    +
    +This module provides RFC 3986 compliant functions for parsing,
    +classifying and composing URIs and URI references, largely replacing
    +the Python Standard Library's ``urllib.parse`` module.
    +
    +.. code-block:: pycon
    +
    +    >>> from uritools import uricompose, urijoin, urisplit, uriunsplit
    +    >>> uricompose(scheme='foo', host='example.com', port=8042,
    +    ...            path='/over/there', query={'name': 'ferret'},
    +    ...            fragment='nose')
    +    'foo://example.com:8042/over/there?name=ferret#nose'
    +    >>> parts = urisplit(_)
    +    >>> parts.scheme
    +    'foo'
    +    >>> parts.authority
    +    'example.com:8042'
    +    >>> parts.getport(default=80)
    +    8042
    +    >>> parts.getquerydict().get('name')
    +    ['ferret']
    +    >>> parts.isuri()
    +    True
    +    >>> parts.isabsuri()
    +    False
    +    >>> urijoin(uriunsplit(parts), '/right/here?name=swallow#beak')
    +    'foo://example.com:8042/right/here?name=swallow#beak'
    +
    +For various reasons, ``urllib.parse`` and its Python 2 predecessor
    +``urlparse`` are not compliant with current Internet standards.  As
    +stated in `Lib/urllib/parse.py
    +`_:
    +
    +    RFC 3986 is considered the current standard and any future changes
    +    to urlparse module should conform with it.  The urlparse module is
    +    currently not entirely compliant with this RFC due to defacto
    +    scenarios for parsing, and for backward compatibility purposes,
    +    some parsing quirks from older RFCs are retained.
    +
    +This module aims to provide fully RFC 3986 compliant replacements for
    +the most commonly used functions found in ``urllib.parse``.  It also
    +includes functions for distinguishing between the different forms of
    +URIs and URI references, and for conveniently creating URIs from their
    +individual components.
    +
    +
    +Installation
    +------------------------------------------------------------------------
    +
    +uritools is available from PyPI_ and can be installed by running::
    +
    +  pip install uritools
    +
    +
    +Project Resources
    +------------------------------------------------------------------------
    +
    +- `Documentation`_
    +- `Issue tracker`_
    +- `Source code`_
    +- `Change log`_
    +
    +
    +License
    +------------------------------------------------------------------------
    +
    +Copyright (c) 2014-2019 Thomas Kemmer.
    +
    +Licensed under the `MIT License`_.
    +
    +
    +.. _PyPI: https://pypi.org/project/uritools/
    +.. _Documentation: https://uritools.readthedocs.io/
    +.. _Issue tracker: https://github.com/tkem/uritools/issues/
    +.. _Source code: https://github.com/tkem/uritools/
    +.. _Change log: https://github.com/tkem/uritools/blob/master/CHANGELOG.rst
    +.. _MIT License: http://raw.github.com/tkem/uritools/master/LICENSE
    +
    +
    diff --git a/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/RECORD b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/RECORD
    new file mode 100644
    index 0000000..8b2c2f7
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/RECORD
    @@ -0,0 +1,22 @@
    +uritools-3.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
    +uritools-3.0.0.dist-info/LICENSE,sha256=fdSWJiwLo3h_fuvwJmPFDDBf9XWoH2kgjhZFc42jz_w,1085
    +uritools-3.0.0.dist-info/METADATA,sha256=TnQW6szwjUe1Qit-53yS0yuO-EkflnJFS28Cd7rRbEQ,4258
    +uritools-3.0.0.dist-info/RECORD,,
    +uritools-3.0.0.dist-info/WHEEL,sha256=_NOXIqFgOaYmlm9RJLPQZ13BJuEIrp5jx5ptRD5uh3Y,92
    +uritools-3.0.0.dist-info/top_level.txt,sha256=Ho67FzoddNX4Z4ukDDQ0v0Mf3jq3dF5HgOCWwotU07g,9
    +uritools/__init__.py,sha256=5bGelXU2iflWtz7DLhQ8I3LuH1sKnacHauBJo79XdNA,952
    +uritools/__pycache__/__init__.cpython-37.pyc,,
    +uritools/__pycache__/chars.cpython-37.pyc,,
    +uritools/__pycache__/classify.cpython-37.pyc,,
    +uritools/__pycache__/compose.cpython-37.pyc,,
    +uritools/__pycache__/defrag.cpython-37.pyc,,
    +uritools/__pycache__/encoding.cpython-37.pyc,,
    +uritools/__pycache__/join.cpython-37.pyc,,
    +uritools/__pycache__/split.cpython-37.pyc,,
    +uritools/chars.py,sha256=aNT5UKzFKrJ1hEO5_6u8So3XYq-SFZdHDDitpEhnB-E,539
    +uritools/classify.py,sha256=wROYKe4t9K3YQZs1IAbVUIcj0EdRUVi7k2c0Q7tumRU,926
    +uritools/compose.py,sha256=Tssd5XaXuDulIdy5qN4xKZTffX_uJKjKHufo4xQPVuQ,8145
    +uritools/defrag.py,sha256=ItlGns_4T3zxKgoK0F3B3pLlkcplFMvCQpAN1bm6ydY,1274
    +uritools/encoding.py,sha256=6srnGFvEgmATxixzn6StgV8AR22zAYjDsSH6oW7KF6w,1575
    +uritools/join.py,sha256=ZIsIL3vTpRIfI50AzNocB6-dlzMNjJbZD9YaEHH62hk,445
    +uritools/split.py,sha256=LPfis-xMkQPhoiIKV6dyxD6eZEDPPnBARGZ_93bPBgA,13665
    diff --git a/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/WHEEL b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/WHEEL
    new file mode 100644
    index 0000000..4eeaea1
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/WHEEL
    @@ -0,0 +1,5 @@
    +Wheel-Version: 1.0
    +Generator: bdist_wheel (0.32.3)
    +Root-Is-Purelib: true
    +Tag: py3-none-any
    +
    diff --git a/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/top_level.txt
    new file mode 100644
    index 0000000..50d5e45
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools-3.0.0.dist-info/top_level.txt
    @@ -0,0 +1 @@
    +uritools
    diff --git a/venv/lib/python3.7/site-packages/uritools/__init__.py b/venv/lib/python3.7/site-packages/uritools/__init__.py
    new file mode 100644
    index 0000000..991b42c
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/__init__.py
    @@ -0,0 +1,40 @@
    +"""RFC 3986 compliant, scheme-agnostic replacement for `urllib.parse`.
    +
    +This module defines RFC 3986 compliant replacements for the most
    +commonly used functions of the Python Standard Library
    +:mod:`urllib.parse` module.
    +
    +"""
    +
    +from .chars import GEN_DELIMS, RESERVED, SUB_DELIMS, UNRESERVED
    +from .classify import isabspath, isabsuri, isnetpath, isrelpath
    +from .classify import issamedoc, isuri
    +from .compose import uricompose
    +from .defrag import DefragResult, uridefrag
    +from .encoding import uridecode, uriencode
    +from .join import urijoin
    +from .split import SplitResult, urisplit, uriunsplit
    +
    +__all__ = (
    +    'GEN_DELIMS',
    +    'RESERVED',
    +    'SUB_DELIMS',
    +    'UNRESERVED',
    +    'DefragResult',
    +    'SplitResult',
    +    'isabspath',
    +    'isabsuri',
    +    'isnetpath',
    +    'isrelpath',
    +    'issamedoc',
    +    'isuri',
    +    'uricompose',
    +    'uridecode',
    +    'uridefrag',
    +    'uriencode',
    +    'urijoin',
    +    'urisplit',
    +    'uriunsplit'
    +)
    +
    +__version__ = '3.0.0'
    diff --git a/venv/lib/python3.7/site-packages/uritools/chars.py b/venv/lib/python3.7/site-packages/uritools/chars.py
    new file mode 100644
    index 0000000..1ba1305
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/chars.py
    @@ -0,0 +1,23 @@
    +# RFC 3986 2.2.  Reserved Characters
    +#
    +#   reserved    = gen-delims / sub-delims
    +#
    +#   gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
    +#
    +#   sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
    +#               / "*" / "+" / "," / ";" / "="
    +#
    +GEN_DELIMS = ':/?#[]@'
    +SUB_DELIMS = "!$&'()*+,;="
    +RESERVED = GEN_DELIMS + SUB_DELIMS
    +
    +# RFC 3986 2.3.  Unreserved Characters
    +#
    +#   unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
    +#
    +UNRESERVED = (
    +    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    +    'abcdefghijklmnopqrstuvwxyz'
    +    '0123456789'
    +    '-._~'
    +)
    diff --git a/venv/lib/python3.7/site-packages/uritools/classify.py b/venv/lib/python3.7/site-packages/uritools/classify.py
    new file mode 100644
    index 0000000..c978ad2
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/classify.py
    @@ -0,0 +1,33 @@
    +from .split import urisplit
    +
    +# TODO: use specialized checks/regexes for performance
    +
    +
    +def isuri(uristring):
    +    """Return :const:`True` if `uristring` is a URI."""
    +    return urisplit(uristring).isuri()
    +
    +
    +def isabsuri(uristring):
    +    """Return :const:`True` if `uristring` is an absolute URI."""
    +    return urisplit(uristring).isabsuri()
    +
    +
    +def isnetpath(uristring):
    +    """Return :const:`True` if `uristring` is a network-path reference."""
    +    return urisplit(uristring).isnetpath()
    +
    +
    +def isabspath(uristring):
    +    """Return :const:`True` if `uristring` is an absolute-path reference."""
    +    return urisplit(uristring).isabspath()
    +
    +
    +def isrelpath(uristring):
    +    """Return :const:`True` if `uristring` is a relative-path reference."""
    +    return urisplit(uristring).isrelpath()
    +
    +
    +def issamedoc(uristring):
    +    """Return :const:`True` if `uristring` is a same-document reference."""
    +    return urisplit(uristring).issamedoc()
    diff --git a/venv/lib/python3.7/site-packages/uritools/compose.py b/venv/lib/python3.7/site-packages/uritools/compose.py
    new file mode 100644
    index 0000000..ffe0112
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/compose.py
    @@ -0,0 +1,204 @@
    +import collections
    +import collections.abc
    +import ipaddress
    +import numbers
    +import re
    +
    +from .chars import SUB_DELIMS
    +from .encoding import uriencode
    +from .split import uriunsplit
    +
    +# RFC 3986 3.1: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
    +_SCHEME_RE = re.compile(b'^[A-Za-z][A-Za-z0-9+.-]*$')
    +
    +# RFC 3986 3.2: authority = [ userinfo "@" ] host [ ":" port ]
    +_AUTHORITY_RE_BYTES = re.compile(b'^(?:(.*)@)?(.*?)(?::([0-9]*))?$')
    +_AUTHORITY_RE_STR = re.compile(u'^(?:(.*)@)?(.*?)(?::([0-9]*))?$')
    +
    +# safe component characters
    +_SAFE_USERINFO = SUB_DELIMS + ':'
    +_SAFE_HOST = SUB_DELIMS
    +_SAFE_PATH = SUB_DELIMS + ':@/'
    +_SAFE_QUERY = SUB_DELIMS + ':@/?'
    +_SAFE_FRAGMENT = SUB_DELIMS + ':@/?'
    +
    +
    +def _scheme(scheme):
    +    if _SCHEME_RE.match(scheme):
    +        return scheme.lower()
    +    else:
    +        raise ValueError('Invalid scheme component')
    +
    +
    +def _authority(userinfo, host, port, encoding):
    +    authority = []
    +
    +    if userinfo is not None:
    +        authority.append(uriencode(userinfo, _SAFE_USERINFO, encoding))
    +        authority.append(b'@')
    +
    +    if isinstance(host, ipaddress.IPv6Address):
    +        authority.append(b'[' + host.compressed.encode() + b']')
    +    elif isinstance(host, ipaddress.IPv4Address):
    +        authority.append(host.compressed.encode())
    +    elif isinstance(host, bytes):
    +        authority.append(_host(host))
    +    elif host is not None:
    +        authority.append(_host(host.encode('utf-8')))
    +
    +    if isinstance(port, numbers.Number):
    +        authority.append(_port(str(port).encode()))
    +    elif isinstance(port, bytes):
    +        authority.append(_port(port))
    +    elif port is not None:
    +        authority.append(_port(port.encode()))
    +
    +    return b''.join(authority) if authority else None
    +
    +
    +def _ip_literal(address):
    +    if address.startswith('v'):
    +        raise ValueError('Address mechanism not supported')
    +    else:
    +        return b'[' + ipaddress.IPv6Address(address).compressed.encode() + b']'
    +
    +
    +def _host(host):
    +    # RFC 3986 3.2.3: Although host is case-insensitive, producers and
    +    # normalizers should use lowercase for registered names and
    +    # hexadecimal addresses for the sake of uniformity, while only
    +    # using uppercase letters for percent-encodings.
    +    if host.startswith(b'[') and host.endswith(b']'):
    +        return _ip_literal(host[1:-1].decode())
    +    # check for IPv6 addresses as returned by SplitResult.gethost()
    +    try:
    +        return _ip_literal(host.decode('utf-8'))
    +    except ValueError:
    +        return uriencode(host, _SAFE_HOST, 'utf-8').lower()
    +
    +
    +def _port(port):
    +    # RFC 3986 3.2.3: URI producers and normalizers should omit the
    +    # port component and its ":" delimiter if port is empty or if its
    +    # value would be the same as that of the scheme's default.
    +    if port.lstrip(b'0123456789'):
    +        raise ValueError('Invalid port subcomponent')
    +    elif port:
    +        return b':' + port
    +    else:
    +        return b''
    +
    +
    +def _querylist(items, sep, encoding):
    +    terms = []
    +    append = terms.append
    +    safe = _SAFE_QUERY.replace(sep, '')
    +    for key, value in items:
    +        name = uriencode(key, safe, encoding)
    +        if value is None:
    +            append(name)
    +        elif isinstance(value, (bytes, str)):
    +            append(name + b'=' + uriencode(value, safe, encoding))
    +        else:
    +            append(name + b'=' + uriencode(str(value), safe, encoding))
    +    return sep.encode('ascii').join(terms)
    +
    +
    +def _querydict(mapping, sep, encoding):
    +    items = []
    +    for key, value in mapping.items():
    +        if isinstance(value, (bytes, str)):
    +            items.append((key, value))
    +        elif isinstance(value, collections.abc.Iterable):
    +            items.extend([(key, v) for v in value])
    +        else:
    +            items.append((key, value))
    +    return _querylist(items, sep, encoding)
    +
    +
    +def uricompose(scheme=None, authority=None, path='', query=None,
    +               fragment=None, userinfo=None, host=None, port=None,
    +               querysep='&', encoding='utf-8'):
    +    """Compose a URI reference string from its individual components."""
    +
    +    # RFC 3986 3.1: Scheme names consist of a sequence of characters
    +    # beginning with a letter and followed by any combination of
    +    # letters, digits, plus ("+"), period ("."), or hyphen ("-").
    +    # Although schemes are case-insensitive, the canonical form is
    +    # lowercase and documents that specify schemes must do so with
    +    # lowercase letters.  An implementation should accept uppercase
    +    # letters as equivalent to lowercase in scheme names (e.g., allow
    +    # "HTTP" as well as "http") for the sake of robustness but should
    +    # only produce lowercase scheme names for consistency.
    +    if isinstance(scheme, bytes):
    +        scheme = _scheme(scheme)
    +    elif scheme is not None:
    +        scheme = _scheme(scheme.encode())
    +
    +    # authority must be string type or three-item iterable
    +    if authority is None:
    +        authority = (None, None, None)
    +    elif isinstance(authority, bytes):
    +        authority = _AUTHORITY_RE_BYTES.match(authority).groups()
    +    elif isinstance(authority, str):
    +        authority = _AUTHORITY_RE_STR.match(authority).groups()
    +    elif not isinstance(authority, collections.abc.Iterable):
    +        raise TypeError('Invalid authority type')
    +    elif len(authority) != 3:
    +        raise ValueError('Invalid authority length')
    +    authority = _authority(
    +        userinfo if userinfo is not None else authority[0],
    +        host if host is not None else authority[1],
    +        port if port is not None else authority[2],
    +        encoding
    +    )
    +
    +    # RFC 3986 3.3: If a URI contains an authority component, then the
    +    # path component must either be empty or begin with a slash ("/")
    +    # character.  If a URI does not contain an authority component,
    +    # then the path cannot begin with two slash characters ("//").
    +    path = uriencode(path, _SAFE_PATH, encoding)
    +    if authority is not None and path and not path.startswith(b'/'):
    +        raise ValueError('Invalid path with authority component')
    +    if authority is None and path.startswith(b'//'):
    +        raise ValueError('Invalid path without authority component')
    +
    +    # RFC 3986 4.2: A path segment that contains a colon character
    +    # (e.g., "this:that") cannot be used as the first segment of a
    +    # relative-path reference, as it would be mistaken for a scheme
    +    # name.  Such a segment must be preceded by a dot-segment (e.g.,
    +    # "./this:that") to make a relative-path reference.
    +    if scheme is None and authority is None and not path.startswith(b'/'):
    +        if b':' in path.partition(b'/')[0]:
    +            path = b'./' + path
    +
    +    # RFC 3986 3.4: The characters slash ("/") and question mark ("?")
    +    # may represent data within the query component.  Beware that some
    +    # older, erroneous implementations may not handle such data
    +    # correctly when it is used as the base URI for relative
    +    # references (Section 5.1), apparently because they fail to
    +    # distinguish query data from path data when looking for
    +    # hierarchical separators.  However, as query components are often
    +    # used to carry identifying information in the form of "key=value"
    +    # pairs and one frequently used value is a reference to another
    +    # URI, it is sometimes better for usability to avoid percent-
    +    # encoding those characters.
    +    if isinstance(query, (bytes, str)):
    +        query = uriencode(query, _SAFE_QUERY, encoding)
    +    elif isinstance(query, collections.abc.Mapping):
    +        query = _querydict(query, querysep, encoding)
    +    elif isinstance(query, collections.abc.Iterable):
    +        query = _querylist(query, querysep, encoding)
    +    elif query is not None:
    +        raise TypeError('Invalid query type')
    +
    +    # RFC 3986 3.5: The characters slash ("/") and question mark ("?")
    +    # are allowed to represent data within the fragment identifier.
    +    # Beware that some older, erroneous implementations may not handle
    +    # this data correctly when it is used as the base URI for relative
    +    # references.
    +    if fragment is not None:
    +        fragment = uriencode(fragment, _SAFE_FRAGMENT, encoding)
    +
    +    # return URI reference as `str`
    +    return uriunsplit((scheme, authority, path, query, fragment)).decode()
    diff --git a/venv/lib/python3.7/site-packages/uritools/defrag.py b/venv/lib/python3.7/site-packages/uritools/defrag.py
    new file mode 100644
    index 0000000..00d8735
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/defrag.py
    @@ -0,0 +1,41 @@
    +import collections
    +
    +from .encoding import uridecode
    +
    +
    +class DefragResult(collections.namedtuple('DefragResult', 'uri fragment')):
    +    """Class to hold :func:`uridefrag` results."""
    +
    +    __slots__ = ()  # prevent creation of instance dictionary
    +
    +    def geturi(self):
    +        """Return the recombined version of the original URI as a string."""
    +        fragment = self.fragment
    +        if fragment is None:
    +            return self.uri
    +        elif isinstance(fragment, bytes):
    +            return self.uri + b'#' + fragment
    +        else:
    +            return self.uri + u'#' + fragment
    +
    +    def getfragment(self, default=None, encoding='utf-8', errors='strict'):
    +        """Return the decoded fragment identifier, or `default` if the
    +        original URI did not contain a fragment component.
    +
    +        """
    +        fragment = self.fragment
    +        if fragment is not None:
    +            return uridecode(fragment, encoding, errors)
    +        else:
    +            return default
    +
    +
    +def uridefrag(uristring):
    +    """Remove an existing fragment component from a URI reference string.
    +
    +    """
    +    if isinstance(uristring, bytes):
    +        parts = uristring.partition(b'#')
    +    else:
    +        parts = uristring.partition(u'#')
    +    return DefragResult(parts[0], parts[2] if parts[1] else None)
    diff --git a/venv/lib/python3.7/site-packages/uritools/encoding.py b/venv/lib/python3.7/site-packages/uritools/encoding.py
    new file mode 100644
    index 0000000..1c60cca
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/encoding.py
    @@ -0,0 +1,53 @@
    +from string import hexdigits as _hex
    +
    +from .chars import UNRESERVED
    +
    +
    +# RFC 3986 2.1: For consistency, URI producers and normalizers should
    +# use uppercase hexadecimal digits for all percent-encodings.
    +def _pctenc(byte):
    +    return ('%%%02X' % byte).encode()
    +
    +
    +_unreserved = frozenset(UNRESERVED.encode())
    +
    +_encoded = {
    +    b'': [bytes([i]) if i in _unreserved else _pctenc(i) for i in range(256)]
    +}
    +
    +_decoded = {
    +    (a + b).encode(): bytes.fromhex(a + b) for a in _hex for b in _hex
    +}
    +
    +
    +def uriencode(uristring, safe='', encoding='utf-8', errors='strict'):
    +    """Encode a URI string or string component."""
    +    if not isinstance(uristring, bytes):
    +        uristring = uristring.encode(encoding, errors)
    +    if not isinstance(safe, bytes):
    +        safe = safe.encode('ascii')
    +    try:
    +        encoded = _encoded[safe]
    +    except KeyError:
    +        encoded = _encoded[b''][:]
    +        for i in safe:
    +            encoded[i] = bytes([i])
    +        _encoded[safe] = encoded
    +    return b''.join(map(encoded.__getitem__, uristring))
    +
    +
    +def uridecode(uristring, encoding='utf-8', errors='strict'):
    +    """Decode a URI string or string component."""
    +    if not isinstance(uristring, bytes):
    +        uristring = uristring.encode(encoding or 'ascii', errors)
    +    parts = uristring.split(b'%')
    +    result = [parts[0]]
    +    append = result.append
    +    decode = _decoded.get
    +    for s in parts[1:]:
    +        append(decode(s[:2], b'%' + s[:2]))
    +        append(s[2:])
    +    if encoding is not None:
    +        return b''.join(result).decode(encoding, errors)
    +    else:
    +        return b''.join(result)
    diff --git a/venv/lib/python3.7/site-packages/uritools/join.py b/venv/lib/python3.7/site-packages/uritools/join.py
    new file mode 100644
    index 0000000..e2f211e
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/join.py
    @@ -0,0 +1,14 @@
    +from .split import urisplit
    +
    +
    +def urijoin(base, ref, strict=False):
    +    """Convert a URI reference relative to a base URI to its target URI
    +    string.
    +
    +    """
    +    if isinstance(base, type(ref)):
    +        return urisplit(base).transform(ref, strict).geturi()
    +    elif isinstance(base, bytes):
    +        return urisplit(base.decode()).transform(ref, strict).geturi()
    +    else:
    +        return urisplit(base).transform(ref.decode(), strict).geturi()
    diff --git a/venv/lib/python3.7/site-packages/uritools/split.py b/venv/lib/python3.7/site-packages/uritools/split.py
    new file mode 100644
    index 0000000..e7aa8c2
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/uritools/split.py
    @@ -0,0 +1,399 @@
    +import collections
    +import collections.abc
    +import ipaddress
    +import re
    +
    +from .encoding import uridecode
    +
    +_URI_COMPONENTS = ('scheme', 'authority', 'path', 'query', 'fragment')
    +
    +
    +def _ip_literal(address):
    +    # RFC 3986 3.2.2: In anticipation of future, as-yet-undefined IP
    +    # literal address formats, an implementation may use an optional
    +    # version flag to indicate such a format explicitly rather than
    +    # rely on heuristic determination.
    +    #
    +    #  IP-literal = "[" ( IPv6address / IPvFuture  ) "]"
    +    #
    +    #  IPvFuture  = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
    +    #
    +    # If a URI containing an IP-literal that starts with "v"
    +    # (case-insensitive), indicating that the version flag is present,
    +    # is dereferenced by an application that does not know the meaning
    +    # of that version flag, then the application should return an
    +    # appropriate error for "address mechanism not supported".
    +    if isinstance(address, bytes):
    +        address = address.decode('ascii')
    +    if address.startswith(u'v'):
    +        raise ValueError('address mechanism not supported')
    +    return ipaddress.IPv6Address(address)
    +
    +
    +def _ipv4_address(address):
    +    try:
    +        if isinstance(address, bytes):
    +            return ipaddress.IPv4Address(address.decode('ascii'))
    +        else:
    +            return ipaddress.IPv4Address(address)
    +    except ValueError:
    +        return None
    +
    +
    +class SplitResult(collections.namedtuple('SplitResult', _URI_COMPONENTS)):
    +    """Base class to hold :func:`urisplit` results."""
    +
    +    __slots__ = ()  # prevent creation of instance dictionary
    +
    +    @property
    +    def userinfo(self):
    +        authority = self.authority
    +        if authority is None:
    +            return None
    +        userinfo, present, _ = authority.rpartition(self.AT)
    +        if present:
    +            return userinfo
    +        else:
    +            return None
    +
    +    @property
    +    def host(self):
    +        authority = self.authority
    +        if authority is None:
    +            return None
    +        _, _, hostinfo = authority.rpartition(self.AT)
    +        host, _, port = hostinfo.rpartition(self.COLON)
    +        if port.lstrip(self.DIGITS):
    +            return hostinfo
    +        else:
    +            return host
    +
    +    @property
    +    def port(self):
    +        authority = self.authority
    +        if authority is None:
    +            return None
    +        _, present, port = authority.rpartition(self.COLON)
    +        if present and not port.lstrip(self.DIGITS):
    +            return port
    +        else:
    +            return None
    +
    +    def geturi(self):
    +        """Return the re-combined version of the original URI reference as a
    +        string.
    +
    +        """
    +        scheme, authority, path, query, fragment = self
    +
    +        # RFC 3986 5.3. Component Recomposition
    +        result = []
    +        if scheme is not None:
    +            result.extend([scheme, self.COLON])
    +        if authority is not None:
    +            result.extend([self.SLASH, self.SLASH, authority])
    +        result.append(path)
    +        if query is not None:
    +            result.extend([self.QUEST, query])
    +        if fragment is not None:
    +            result.extend([self.HASH, fragment])
    +        return self.EMPTY.join(result)
    +
    +    def getscheme(self, default=None):
    +        """Return the URI scheme in canonical (lowercase) form, or `default`
    +        if the original URI reference did not contain a scheme component.
    +
    +        """
    +        scheme = self.scheme
    +        if scheme is None:
    +            return default
    +        elif isinstance(scheme, bytes):
    +            return scheme.decode('ascii').lower()
    +        else:
    +            return scheme.lower()
    +
    +    def getauthority(self, default=None, encoding='utf-8', errors='strict'):
    +        """Return the decoded userinfo, host and port subcomponents of the URI
    +        authority as a three-item tuple.
    +
    +        """
    +        # TBD: (userinfo, host, port) kwargs, default string?
    +        if default is None:
    +            default = (None, None, None)
    +        elif not isinstance(default, collections.abc.Iterable):
    +            raise TypeError('Invalid default type')
    +        elif len(default) != 3:
    +            raise ValueError('Invalid default length')
    +        # TODO: this could be much more efficient by using a dedicated regex
    +        return (
    +            self.getuserinfo(default[0], encoding, errors),
    +            self.gethost(default[1], errors),
    +            self.getport(default[2])
    +        )
    +
    +    def getuserinfo(self, default=None, encoding='utf-8', errors='strict'):
    +        """Return the decoded userinfo subcomponent of the URI authority, or
    +        `default` if the original URI reference did not contain a
    +        userinfo field.
    +
    +        """
    +        userinfo = self.userinfo
    +        if userinfo is None:
    +            return default
    +        else:
    +            return uridecode(userinfo, encoding, errors)
    +
    +    def gethost(self, default=None, errors='strict'):
    +        """Return the decoded host subcomponent of the URI authority as a
    +        string or an :mod:`ipaddress` address object, or `default` if
    +        the original URI reference did not contain a host.
    +
    +        """
    +        host = self.host
    +        if host is None or (not host and default is not None):
    +            return default
    +        elif host.startswith(self.LBRACKET) and host.endswith(self.RBRACKET):
    +            return _ip_literal(host[1:-1])
    +        elif host.startswith(self.LBRACKET) or host.endswith(self.RBRACKET):
    +            raise ValueError('Invalid host %r' % host)
    +        # TODO: faster check for IPv4 address?
    +        return _ipv4_address(host) or uridecode(host, 'utf-8', errors).lower()
    +
    +    def getport(self, default=None):
    +        """Return the port subcomponent of the URI authority as an
    +        :class:`int`, or `default` if the original URI reference did
    +        not contain a port or if the port was empty.
    +
    +        """
    +        port = self.port
    +        if port:
    +            return int(port)
    +        else:
    +            return default
    +
    +    def getpath(self, encoding='utf-8', errors='strict'):
    +        """Return the normalized decoded URI path."""
    +        path = self.__remove_dot_segments(self.path)
    +        return uridecode(path, encoding, errors)
    +
    +    def getquery(self, default=None, encoding='utf-8', errors='strict'):
    +        """Return the decoded query string, or `default` if the original URI
    +        reference did not contain a query component.
    +
    +        """
    +        query = self.query
    +        if query is None:
    +            return default
    +        else:
    +            return uridecode(query, encoding, errors)
    +
    +    def getquerydict(self, sep='&', encoding='utf-8', errors='strict'):
    +        """Split the query component into individual `name=value` pairs
    +        separated by `sep` and return a dictionary of query variables.
    +        The dictionary keys are the unique query variable names and
    +        the values are lists of values for each name.
    +
    +        """
    +        dict = collections.defaultdict(list)
    +        for name, value in self.getquerylist(sep, encoding, errors):
    +            dict[name].append(value)
    +        return dict
    +
    +    def getquerylist(self, sep='&', encoding='utf-8', errors='strict'):
    +        """Split the query component into individual `name=value` pairs
    +        separated by `sep`, and return a list of `(name, value)`
    +        tuples.
    +
    +        """
    +        if not self.query:
    +            return []
    +        elif isinstance(sep, type(self.query)):
    +            qsl = self.query.split(sep)
    +        elif isinstance(sep, bytes):
    +            qsl = self.query.split(sep.decode('ascii'))
    +        else:
    +            qsl = self.query.split(sep.encode('ascii'))
    +        items = []
    +        for parts in [qs.partition(self.EQ) for qs in qsl if qs]:
    +            name = uridecode(parts[0], encoding, errors)
    +            if parts[1]:
    +                value = uridecode(parts[2], encoding, errors)
    +            else:
    +                value = None
    +            items.append((name, value))
    +        return items
    +
    +    def getfragment(self, default=None, encoding='utf-8', errors='strict'):
    +        """Return the decoded fragment identifier, or `default` if the
    +        original URI reference did not contain a fragment component.
    +
    +        """
    +        fragment = self.fragment
    +        if fragment is None:
    +            return default
    +        else:
    +            return uridecode(fragment, encoding, errors)
    +
    +    def isuri(self):
    +        """Return :const:`True` if this is a URI."""
    +        return self.scheme is not None
    +
    +    def isabsuri(self):
    +        """Return :const:`True` if this is an absolute URI."""
    +        return self.scheme is not None and self.fragment is None
    +
    +    def isnetpath(self):
    +        """Return :const:`True` if this is a network-path reference."""
    +        return self.scheme is None and self.authority is not None
    +
    +    def isabspath(self):
    +        """Return :const:`True` if this is an absolute-path reference."""
    +        return (self.scheme is None and self.authority is None and
    +                self.path.startswith(self.SLASH))
    +
    +    def isrelpath(self):
    +        """Return :const:`True` if this is a relative-path reference."""
    +        return (self.scheme is None and self.authority is None and
    +                not self.path.startswith(self.SLASH))
    +
    +    def issamedoc(self):
    +        """Return :const:`True` if this is a same-document reference."""
    +        return (self.scheme is None and self.authority is None and
    +                not self.path and self.query is None)
    +
    +    def transform(self, ref, strict=False):
    +        """Transform a URI reference relative to `self` into a
    +        :class:`SplitResult` representing its target URI.
    +
    +        """
    +        scheme, authority, path, query, fragment = self.RE.match(ref).groups()
    +
    +        # RFC 3986 5.2.2. Transform References
    +        if scheme is not None and (strict or scheme != self.scheme):
    +            path = self.__remove_dot_segments(path)
    +        elif authority is not None:
    +            scheme = self.scheme
    +            path = self.__remove_dot_segments(path)
    +        elif not path:
    +            scheme = self.scheme
    +            authority = self.authority
    +            path = self.path
    +            query = self.query if query is None else query
    +        elif path.startswith(self.SLASH):
    +            scheme = self.scheme
    +            authority = self.authority
    +            path = self.__remove_dot_segments(path)
    +        else:
    +            scheme = self.scheme
    +            authority = self.authority
    +            path = self.__remove_dot_segments(self.__merge(path))
    +        return type(self)(scheme, authority, path, query, fragment)
    +
    +    def __merge(self, path):
    +        # RFC 3986 5.2.3. Merge Paths
    +        if self.authority is not None and not self.path:
    +            return self.SLASH + path
    +        else:
    +            parts = self.path.rpartition(self.SLASH)
    +            return parts[1].join((parts[0], path))
    +
    +    @classmethod
    +    def __remove_dot_segments(cls, path):
    +        # RFC 3986 5.2.4. Remove Dot Segments
    +        pseg = []
    +        for s in path.split(cls.SLASH):
    +            if s == cls.DOT:
    +                continue
    +            elif s != cls.DOTDOT:
    +                pseg.append(s)
    +            elif len(pseg) == 1 and not pseg[0]:
    +                continue
    +            elif pseg and pseg[-1] != cls.DOTDOT:
    +                pseg.pop()
    +            else:
    +                pseg.append(s)
    +        # adjust for trailing '/.' or '/..'
    +        if path.rpartition(cls.SLASH)[2] in (cls.DOT, cls.DOTDOT):
    +            pseg.append(cls.EMPTY)
    +        if path and len(pseg) == 1 and pseg[0] == cls.EMPTY:
    +            pseg.insert(0, cls.DOT)
    +        return cls.SLASH.join(pseg)
    +
    +
    +class SplitResultBytes(SplitResult):
    +
    +    __slots__ = ()  # prevent creation of instance dictionary
    +
    +    # RFC 3986 Appendix B
    +    RE = re.compile(br"""
    +    (?:([A-Za-z][A-Za-z0-9+.-]*):)?  # scheme (RFC 3986 3.1)
    +    (?://([^/?#]*))?                 # authority
    +    ([^?#]*)                         # path
    +    (?:\?([^#]*))?                   # query
    +    (?:\#(.*))?                      # fragment
    +    """, flags=re.VERBOSE)
    +
    +    # RFC 3986 2.2 gen-delims
    +    COLON, SLASH, QUEST, HASH, LBRACKET, RBRACKET, AT = (
    +        b':', b'/', b'?', b'#', b'[', b']', b'@'
    +    )
    +
    +    # RFC 3986 3.3 dot-segments
    +    DOT, DOTDOT = b'.', b'..'
    +
    +    EMPTY, EQ = b'', b'='
    +
    +    DIGITS = b'0123456789'
    +
    +
    +class SplitResultString(SplitResult):
    +
    +    __slots__ = ()  # prevent creation of instance dictionary
    +
    +    # RFC 3986 Appendix B
    +    RE = re.compile(r"""
    +    (?:([A-Za-z][A-Za-z0-9+.-]*):)?  # scheme (RFC 3986 3.1)
    +    (?://([^/?#]*))?                 # authority
    +    ([^?#]*)                         # path
    +    (?:\?([^#]*))?                   # query
    +    (?:\#(.*))?                      # fragment
    +    """, flags=re.VERBOSE)
    +
    +    # RFC 3986 2.2 gen-delims
    +    COLON, SLASH, QUEST, HASH, LBRACKET, RBRACKET, AT = (
    +        u':', u'/', u'?', u'#', u'[', u']', u'@'
    +    )
    +
    +    # RFC 3986 3.3 dot-segments
    +    DOT, DOTDOT = u'.', u'..'
    +
    +    EMPTY, EQ = u'', u'='
    +
    +    DIGITS = u'0123456789'
    +
    +
    +def urisplit(uristring):
    +    """Split a well-formed URI reference string into a tuple with five
    +    components corresponding to a URI's general structure::
    +
    +      :///?#
    +
    +    """
    +    if isinstance(uristring, bytes):
    +        result = SplitResultBytes
    +    else:
    +        result = SplitResultString
    +    return result(*result.RE.match(uristring).groups())
    +
    +
    +def uriunsplit(parts):
    +    """Combine the elements of a five-item iterable into a URI reference's
    +    string representation.
    +
    +    """
    +    scheme, authority, path, query, fragment = parts
    +    if isinstance(path, bytes):
    +        result = SplitResultBytes
    +    else:
    +        result = SplitResultString
    +    return result(scheme, authority, path, query, fragment).geturi()
    diff --git a/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/INSTALLER b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/INSTALLER
    new file mode 100644
    index 0000000..a1b589e
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/INSTALLER
    @@ -0,0 +1 @@
    +pip
    diff --git a/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/LICENSE.txt b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/LICENSE.txt
    new file mode 100644
    index 0000000..c89cf27
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/LICENSE.txt
    @@ -0,0 +1,21 @@
    +MIT License
    +
    +Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
    +
    +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/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/METADATA b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/METADATA
    new file mode 100644
    index 0000000..eac8277
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/METADATA
    @@ -0,0 +1,1229 @@
    +Metadata-Version: 2.1
    +Name: urllib3
    +Version: 1.25.7
    +Summary: HTTP library with thread-safe connection pooling, file post, and more.
    +Home-page: https://urllib3.readthedocs.io/
    +Author: Andrey Petrov
    +Author-email: andrey.petrov@shazow.net
    +License: MIT
    +Project-URL: Documentation, https://urllib3.readthedocs.io/
    +Project-URL: Code, https://github.com/urllib3/urllib3
    +Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues
    +Keywords: urllib httplib threadsafe filepost http https ssl pooling
    +Platform: UNKNOWN
    +Classifier: Environment :: Web Environment
    +Classifier: Intended Audience :: Developers
    +Classifier: License :: OSI Approved :: MIT License
    +Classifier: Operating System :: OS Independent
    +Classifier: Programming Language :: Python
    +Classifier: Programming Language :: Python :: 2
    +Classifier: Programming Language :: Python :: 2.7
    +Classifier: Programming Language :: Python :: 3
    +Classifier: Programming Language :: Python :: 3.4
    +Classifier: Programming Language :: Python :: 3.5
    +Classifier: Programming Language :: Python :: 3.6
    +Classifier: Programming Language :: Python :: 3.7
    +Classifier: Programming Language :: Python :: 3.8
    +Classifier: Programming Language :: Python :: Implementation :: CPython
    +Classifier: Programming Language :: Python :: Implementation :: PyPy
    +Classifier: Topic :: Internet :: WWW/HTTP
    +Classifier: Topic :: Software Development :: Libraries
    +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4
    +Provides-Extra: brotli
    +Requires-Dist: brotlipy (>=0.6.0) ; extra == 'brotli'
    +Provides-Extra: secure
    +Requires-Dist: pyOpenSSL (>=0.14) ; extra == 'secure'
    +Requires-Dist: cryptography (>=1.3.4) ; extra == 'secure'
    +Requires-Dist: idna (>=2.0.0) ; extra == 'secure'
    +Requires-Dist: certifi ; extra == 'secure'
    +Requires-Dist: ipaddress ; (python_version == "2.7") and extra == 'secure'
    +Provides-Extra: socks
    +Requires-Dist: PySocks (!=1.5.7,<2.0,>=1.5.6) ; extra == 'socks'
    +
    +urllib3
    +=======
    +
    +urllib3 is a powerful, *sanity-friendly* HTTP client for Python. Much of the
    +Python ecosystem already uses urllib3 and you should too.
    +urllib3 brings many critical features that are missing from the Python
    +standard libraries:
    +
    +- Thread safety.
    +- Connection pooling.
    +- Client-side SSL/TLS verification.
    +- File uploads with multipart encoding.
    +- Helpers for retrying requests and dealing with HTTP redirects.
    +- Support for gzip, deflate, and brotli encoding.
    +- Proxy support for HTTP and SOCKS.
    +- 100% test coverage.
    +
    +urllib3 is powerful and easy to use::
    +
    +    >>> import urllib3
    +    >>> http = urllib3.PoolManager()
    +    >>> r = http.request('GET', 'http://httpbin.org/robots.txt')
    +    >>> r.status
    +    200
    +    >>> r.data
    +    'User-agent: *\nDisallow: /deny\n'
    +
    +
    +Installing
    +----------
    +
    +urllib3 can be installed with `pip `_::
    +
    +    $ pip install urllib3
    +
    +Alternatively, you can grab the latest source code from `GitHub `_::
    +
    +    $ git clone git://github.com/urllib3/urllib3.git
    +    $ python setup.py install
    +
    +
    +Documentation
    +-------------
    +
    +urllib3 has usage and reference documentation at `urllib3.readthedocs.io `_.
    +
    +
    +Contributing
    +------------
    +
    +urllib3 happily accepts contributions. Please see our
    +`contributing documentation `_
    +for some tips on getting started.
    +
    +
    +Security Disclosures
    +--------------------
    +
    +To report a security vulnerability, please use the
    +`Tidelift security contact `_.
    +Tidelift will coordinate the fix and disclosure with maintainers.
    +
    +Maintainers
    +-----------
    +
    +- `@sethmlarson `_ (Seth M. Larson)
    +- `@theacodes `_ (Thea Flowers)
    +- `@haikuginger `_ (Jess Shapiro)
    +- `@lukasa `_ (Cory Benfield)
    +- `@sigmavirus24 `_ (Ian Stapleton Cordasco)
    +- `@shazow `_ (Andrey Petrov)
    +
    +👋
    +
    +
    +Sponsorship
    +-----------
    +
    +.. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png
    +   :width: 75
    +   :alt: Tidelift
    +
    +.. list-table::
    +   :widths: 10 100
    +
    +   * - |tideliftlogo|
    +     - Professional support for urllib3 is available as part of the `Tidelift
    +       Subscription`_.  Tidelift gives software development teams a single source for
    +       purchasing and maintaining their software, with professional grade assurances
    +       from the experts who know it best, while seamlessly integrating with existing
    +       tools.
    +
    +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme
    +
    +If your company benefits from this library, please consider `sponsoring its
    +development `_.
    +
    +Sponsors include:
    +
    +- Abbott (2018-2019), sponsored `@sethmlarson `_'s work on urllib3.
    +- Google Cloud Platform (2018-2019), sponsored `@theacodes `_'s work on urllib3.
    +- Akamai (2017-2018), sponsored `@haikuginger `_'s work on urllib3
    +- Hewlett Packard Enterprise (2016-2017), sponsored `@Lukasa’s `_ work on urllib3.
    +
    +
    +Changes
    +=======
    +
    +1.25.7 (2019-11-11)
    +-------------------
    +
    +* Preserve ``chunked`` parameter on retries (Pull #1715, Pull #1734)
    +
    +* Allow unset ``SERVER_SOFTWARE`` in App Engine (Pull #1704, Issue #1470)
    +
    +* Fix issue where URL fragment was sent within the request target. (Pull #1732)
    +
    +* Fix issue where an empty query section in a URL would fail to parse. (Pull #1732)
    +
    +* Remove TLS 1.3 support in SecureTransport due to Apple removing support (Pull #1703)
    +
    +
    +1.25.6 (2019-09-24)
    +-------------------
    +
    +* Fix issue where tilde (``~``) characters were incorrectly
    +  percent-encoded in the path. (Pull #1692)
    +
    +
    +1.25.5 (2019-09-19)
    +-------------------
    +
    +* Add mitigation for BPO-37428 affecting Python <3.7.4 and OpenSSL 1.1.1+ which
    +  caused certificate verification to be enabled when using ``cert_reqs=CERT_NONE``.
    +  (Issue #1682)
    +
    +
    +1.25.4 (2019-09-19)
    +-------------------
    +
    +* Propagate Retry-After header settings to subsequent retries. (Pull #1607)
    +
    +* Fix edge case where Retry-After header was still respected even when
    +  explicitly opted out of. (Pull #1607)
    +
    +* Remove dependency on ``rfc3986`` for URL parsing.
    +
    +* Fix issue where URLs containing invalid characters within ``Url.auth`` would
    +  raise an exception instead of percent-encoding those characters.
    +
    +* Add support for ``HTTPResponse.auto_close = False`` which makes HTTP responses
    +  work well with BufferedReaders and other ``io`` module features. (Pull #1652)
    +
    +* Percent-encode invalid characters in URL for ``HTTPConnectionPool.request()`` (Pull #1673)
    +
    +
    +1.25.3 (2019-05-23)
    +-------------------
    +
    +* Change ``HTTPSConnection`` to load system CA certificates
    +  when ``ca_certs``, ``ca_cert_dir``, and ``ssl_context`` are
    +  unspecified. (Pull #1608, Issue #1603)
    +
    +* Upgrade bundled rfc3986 to v1.3.2. (Pull #1609, Issue #1605)
    +
    +
    +1.25.2 (2019-04-28)
    +-------------------
    +
    +* Change ``is_ipaddress`` to not detect IPvFuture addresses. (Pull #1583)
    +
    +* Change ``parse_url`` to percent-encode invalid characters within the
    +  path, query, and target components. (Pull #1586)
    +
    +
    +1.25.1 (2019-04-24)
    +-------------------
    +
    +* Add support for Google's ``Brotli`` package. (Pull #1572, Pull #1579)
    +
    +* Upgrade bundled rfc3986 to v1.3.1 (Pull #1578)
    +
    +
    +1.25 (2019-04-22)
    +-----------------
    +
    +* Require and validate certificates by default when using HTTPS (Pull #1507)
    +
    +* Upgraded ``urllib3.utils.parse_url()`` to be RFC 3986 compliant. (Pull #1487)
    +
    +* Added support for ``key_password`` for ``HTTPSConnectionPool`` to use
    +  encrypted ``key_file`` without creating your own ``SSLContext`` object. (Pull #1489)
    +
    +* Add TLSv1.3 support to CPython, pyOpenSSL, and SecureTransport ``SSLContext``
    +  implementations. (Pull #1496)
    +
    +* Switched the default multipart header encoder from RFC 2231 to HTML 5 working draft. (Issue #303, PR #1492)
    +
    +* Fixed issue where OpenSSL would block if an encrypted client private key was
    +  given and no password was given. Instead an ``SSLError`` is raised. (Pull #1489)
    +
    +* Added support for Brotli content encoding. It is enabled automatically if
    +  ``brotlipy`` package is installed which can be requested with
    +  ``urllib3[brotli]`` extra. (Pull #1532)
    +
    +* Drop ciphers using DSS key exchange from default TLS cipher suites.
    +  Improve default ciphers when using SecureTransport. (Pull #1496)
    +
    +* Implemented a more efficient ``HTTPResponse.__iter__()`` method. (Issue #1483)
    +
    +1.24.3 (2019-05-01)
    +-------------------
    +
    +* Apply fix for CVE-2019-9740. (Pull #1591)
    +
    +1.24.2 (2019-04-17)
    +-------------------
    +
    +* Don't load system certificates by default when any other ``ca_certs``, ``ca_certs_dir`` or
    +  ``ssl_context`` parameters are specified.
    +
    +* Remove Authorization header regardless of case when redirecting to cross-site. (Issue #1510)
    +
    +* Add support for IPv6 addresses in subjectAltName section of certificates. (Issue #1269)
    +
    +
    +1.24.1 (2018-11-02)
    +-------------------
    +
    +* Remove quadratic behavior within ``GzipDecoder.decompress()`` (Issue #1467)
    +
    +* Restored functionality of ``ciphers`` parameter for ``create_urllib3_context()``. (Issue #1462)
    +
    +
    +1.24 (2018-10-16)
    +-----------------
    +
    +* Allow key_server_hostname to be specified when initializing a PoolManager to allow custom SNI to be overridden. (Pull #1449)
    +
    +* Test against Python 3.7 on AppVeyor. (Pull #1453)
    +
    +* Early-out ipv6 checks when running on App Engine. (Pull #1450)
    +
    +* Change ambiguous description of backoff_factor (Pull #1436)
    +
    +* Add ability to handle multiple Content-Encodings (Issue #1441 and Pull #1442)
    +
    +* Skip DNS names that can't be idna-decoded when using pyOpenSSL (Issue #1405).
    +
    +* Add a server_hostname parameter to HTTPSConnection which allows for
    +  overriding the SNI hostname sent in the handshake. (Pull #1397)
    +
    +* Drop support for EOL Python 2.6 (Pull #1429 and Pull #1430)
    +
    +* Fixed bug where responses with header Content-Type: message/* erroneously
    +  raised HeaderParsingError, resulting in a warning being logged. (Pull #1439)
    +
    +* Move urllib3 to src/urllib3 (Pull #1409)
    +
    +
    +1.23 (2018-06-04)
    +-----------------
    +
    +* Allow providing a list of headers to strip from requests when redirecting
    +  to a different host. Defaults to the ``Authorization`` header. Different
    +  headers can be set via ``Retry.remove_headers_on_redirect``. (Issue #1316)
    +
    +* Fix ``util.selectors._fileobj_to_fd`` to accept ``long`` (Issue #1247).
    +
    +* Dropped Python 3.3 support. (Pull #1242)
    +
    +* Put the connection back in the pool when calling stream() or read_chunked() on
    +  a chunked HEAD response. (Issue #1234)
    +
    +* Fixed pyOpenSSL-specific ssl client authentication issue when clients
    +  attempted to auth via certificate + chain (Issue #1060)
    +
    +* Add the port to the connectionpool connect print (Pull #1251)
    +
    +* Don't use the ``uuid`` module to create multipart data boundaries. (Pull #1380)
    +
    +* ``read_chunked()`` on a closed response returns no chunks. (Issue #1088)
    +
    +* Add Python 2.6 support to ``contrib.securetransport`` (Pull #1359)
    +
    +* Added support for auth info in url for SOCKS proxy (Pull #1363)
    +
    +
    +1.22 (2017-07-20)
    +-----------------
    +
    +* Fixed missing brackets in ``HTTP CONNECT`` when connecting to IPv6 address via
    +  IPv6 proxy. (Issue #1222)
    +
    +* Made the connection pool retry on ``SSLError``.  The original ``SSLError``
    +  is available on ``MaxRetryError.reason``. (Issue #1112)
    +
    +* Drain and release connection before recursing on retry/redirect.  Fixes
    +  deadlocks with a blocking connectionpool. (Issue #1167)
    +
    +* Fixed compatibility for cookiejar. (Issue #1229)
    +
    +* pyopenssl: Use vendored version of ``six``. (Issue #1231)
    +
    +
    +1.21.1 (2017-05-02)
    +-------------------
    +
    +* Fixed SecureTransport issue that would cause long delays in response body
    +  delivery. (Pull #1154)
    +
    +* Fixed regression in 1.21 that threw exceptions when users passed the
    +  ``socket_options`` flag to the ``PoolManager``.  (Issue #1165)
    +
    +* Fixed regression in 1.21 that threw exceptions when users passed the
    +  ``assert_hostname`` or ``assert_fingerprint`` flag to the ``PoolManager``.
    +  (Pull #1157)
    +
    +
    +1.21 (2017-04-25)
    +-----------------
    +
    +* Improved performance of certain selector system calls on Python 3.5 and
    +  later. (Pull #1095)
    +
    +* Resolved issue where the PyOpenSSL backend would not wrap SysCallError
    +  exceptions appropriately when sending data. (Pull #1125)
    +
    +* Selectors now detects a monkey-patched select module after import for modules
    +  that patch the select module like eventlet, greenlet. (Pull #1128)
    +
    +* Reduced memory consumption when streaming zlib-compressed responses
    +  (as opposed to raw deflate streams). (Pull #1129)
    +
    +* Connection pools now use the entire request context when constructing the
    +  pool key. (Pull #1016)
    +
    +* ``PoolManager.connection_from_*`` methods now accept a new keyword argument,
    +  ``pool_kwargs``, which are merged with the existing ``connection_pool_kw``.
    +  (Pull #1016)
    +
    +* Add retry counter for ``status_forcelist``. (Issue #1147)
    +
    +* Added ``contrib`` module for using SecureTransport on macOS:
    +  ``urllib3.contrib.securetransport``.  (Pull #1122)
    +
    +* urllib3 now only normalizes the case of ``http://`` and ``https://`` schemes:
    +  for schemes it does not recognise, it assumes they are case-sensitive and
    +  leaves them unchanged.
    +  (Issue #1080)
    +
    +
    +1.20 (2017-01-19)
    +-----------------
    +
    +* Added support for waiting for I/O using selectors other than select,
    +  improving urllib3's behaviour with large numbers of concurrent connections.
    +  (Pull #1001)
    +
    +* Updated the date for the system clock check. (Issue #1005)
    +
    +* ConnectionPools now correctly consider hostnames to be case-insensitive.
    +  (Issue #1032)
    +
    +* Outdated versions of PyOpenSSL now cause the PyOpenSSL contrib module
    +  to fail when it is injected, rather than at first use. (Pull #1063)
    +
    +* Outdated versions of cryptography now cause the PyOpenSSL contrib module
    +  to fail when it is injected, rather than at first use. (Issue #1044)
    +
    +* Automatically attempt to rewind a file-like body object when a request is
    +  retried or redirected. (Pull #1039)
    +
    +* Fix some bugs that occur when modules incautiously patch the queue module.
    +  (Pull #1061)
    +
    +* Prevent retries from occurring on read timeouts for which the request method
    +  was not in the method whitelist. (Issue #1059)
    +
    +* Changed the PyOpenSSL contrib module to lazily load idna to avoid
    +  unnecessarily bloating the memory of programs that don't need it. (Pull
    +  #1076)
    +
    +* Add support for IPv6 literals with zone identifiers. (Pull #1013)
    +
    +* Added support for socks5h:// and socks4a:// schemes when working with SOCKS
    +  proxies, and controlled remote DNS appropriately. (Issue #1035)
    +
    +
    +1.19.1 (2016-11-16)
    +-------------------
    +
    +* Fixed AppEngine import that didn't function on Python 3.5. (Pull #1025)
    +
    +
    +1.19 (2016-11-03)
    +-----------------
    +
    +* urllib3 now respects Retry-After headers on 413, 429, and 503 responses when
    +  using the default retry logic. (Pull #955)
    +
    +* Remove markers from setup.py to assist ancient setuptools versions. (Issue
    +  #986)
    +
    +* Disallow superscripts and other integerish things in URL ports. (Issue #989)
    +
    +* Allow urllib3's HTTPResponse.stream() method to continue to work with
    +  non-httplib underlying FPs. (Pull #990)
    +
    +* Empty filenames in multipart headers are now emitted as such, rather than
    +  being suppressed. (Issue #1015)
    +
    +* Prefer user-supplied Host headers on chunked uploads. (Issue #1009)
    +
    +
    +1.18.1 (2016-10-27)
    +-------------------
    +
    +* CVE-2016-9015. Users who are using urllib3 version 1.17 or 1.18 along with
    +  PyOpenSSL injection and OpenSSL 1.1.0 *must* upgrade to this version. This
    +  release fixes a vulnerability whereby urllib3 in the above configuration
    +  would silently fail to validate TLS certificates due to erroneously setting
    +  invalid flags in OpenSSL's ``SSL_CTX_set_verify`` function. These erroneous
    +  flags do not cause a problem in OpenSSL versions before 1.1.0, which
    +  interprets the presence of any flag as requesting certificate validation.
    +
    +  There is no PR for this patch, as it was prepared for simultaneous disclosure
    +  and release. The master branch received the same fix in PR #1010.
    +
    +
    +1.18 (2016-09-26)
    +-----------------
    +
    +* Fixed incorrect message for IncompleteRead exception. (PR #973)
    +
    +* Accept ``iPAddress`` subject alternative name fields in TLS certificates.
    +  (Issue #258)
    +
    +* Fixed consistency of ``HTTPResponse.closed`` between Python 2 and 3.
    +  (Issue #977)
    +
    +* Fixed handling of wildcard certificates when using PyOpenSSL. (Issue #979)
    +
    +
    +1.17 (2016-09-06)
    +-----------------
    +
    +* Accept ``SSLContext`` objects for use in SSL/TLS negotiation. (Issue #835)
    +
    +* ConnectionPool debug log now includes scheme, host, and port. (Issue #897)
    +
    +* Substantially refactored documentation. (Issue #887)
    +
    +* Used URLFetch default timeout on AppEngine, rather than hardcoding our own.
    +  (Issue #858)
    +
    +* Normalize the scheme and host in the URL parser (Issue #833)
    +
    +* ``HTTPResponse`` contains the last ``Retry`` object, which now also
    +  contains retries history. (Issue #848)
    +
    +* Timeout can no longer be set as boolean, and must be greater than zero.
    +  (PR #924)
    +
    +* Removed pyasn1 and ndg-httpsclient from dependencies used for PyOpenSSL. We
    +  now use cryptography and idna, both of which are already dependencies of
    +  PyOpenSSL. (PR #930)
    +
    +* Fixed infinite loop in ``stream`` when amt=None. (Issue #928)
    +
    +* Try to use the operating system's certificates when we are using an
    +  ``SSLContext``. (PR #941)
    +
    +* Updated cipher suite list to allow ChaCha20+Poly1305. AES-GCM is preferred to
    +  ChaCha20, but ChaCha20 is then preferred to everything else. (PR #947)
    +
    +* Updated cipher suite list to remove 3DES-based cipher suites. (PR #958)
    +
    +* Removed the cipher suite fallback to allow HIGH ciphers. (PR #958)
    +
    +* Implemented ``length_remaining`` to determine remaining content
    +  to be read. (PR #949)
    +
    +* Implemented ``enforce_content_length`` to enable exceptions when
    +  incomplete data chunks are received. (PR #949)
    +
    +* Dropped connection start, dropped connection reset, redirect, forced retry,
    +  and new HTTPS connection log levels to DEBUG, from INFO. (PR #967)
    +
    +
    +1.16 (2016-06-11)
    +-----------------
    +
    +* Disable IPv6 DNS when IPv6 connections are not possible. (Issue #840)
    +
    +* Provide ``key_fn_by_scheme`` pool keying mechanism that can be
    +  overridden. (Issue #830)
    +
    +* Normalize scheme and host to lowercase for pool keys, and include
    +  ``source_address``. (Issue #830)
    +
    +* Cleaner exception chain in Python 3 for ``_make_request``.
    +  (Issue #861)
    +
    +* Fixed installing ``urllib3[socks]`` extra. (Issue #864)
    +
    +* Fixed signature of ``ConnectionPool.close`` so it can actually safely be
    +  called by subclasses. (Issue #873)
    +
    +* Retain ``release_conn`` state across retries. (Issues #651, #866)
    +
    +* Add customizable ``HTTPConnectionPool.ResponseCls``, which defaults to
    +  ``HTTPResponse`` but can be replaced with a subclass. (Issue #879)
    +
    +
    +1.15.1 (2016-04-11)
    +-------------------
    +
    +* Fix packaging to include backports module. (Issue #841)
    +
    +
    +1.15 (2016-04-06)
    +-----------------
    +
    +* Added Retry(raise_on_status=False). (Issue #720)
    +
    +* Always use setuptools, no more distutils fallback. (Issue #785)
    +
    +* Dropped support for Python 3.2. (Issue #786)
    +
    +* Chunked transfer encoding when requesting with ``chunked=True``.
    +  (Issue #790)
    +
    +* Fixed regression with IPv6 port parsing. (Issue #801)
    +
    +* Append SNIMissingWarning messages to allow users to specify it in
    +  the PYTHONWARNINGS environment variable. (Issue #816)
    +
    +* Handle unicode headers in Py2. (Issue #818)
    +
    +* Log certificate when there is a hostname mismatch. (Issue #820)
    +
    +* Preserve order of request/response headers. (Issue #821)
    +
    +
    +1.14 (2015-12-29)
    +-----------------
    +
    +* contrib: SOCKS proxy support! (Issue #762)
    +
    +* Fixed AppEngine handling of transfer-encoding header and bug
    +  in Timeout defaults checking. (Issue #763)
    +
    +
    +1.13.1 (2015-12-18)
    +-------------------
    +
    +* Fixed regression in IPv6 + SSL for match_hostname. (Issue #761)
    +
    +
    +1.13 (2015-12-14)
    +-----------------
    +
    +* Fixed ``pip install urllib3[secure]`` on modern pip. (Issue #706)
    +
    +* pyopenssl: Fixed SSL3_WRITE_PENDING error. (Issue #717)
    +
    +* pyopenssl: Support for TLSv1.1 and TLSv1.2. (Issue #696)
    +
    +* Close connections more defensively on exception. (Issue #734)
    +
    +* Adjusted ``read_chunked`` to handle gzipped, chunk-encoded bodies without
    +  repeatedly flushing the decoder, to function better on Jython. (Issue #743)
    +
    +* Accept ``ca_cert_dir`` for SSL-related PoolManager configuration. (Issue #758)
    +
    +
    +1.12 (2015-09-03)
    +-----------------
    +
    +* Rely on ``six`` for importing ``httplib`` to work around
    +  conflicts with other Python 3 shims. (Issue #688)
    +
    +* Add support for directories of certificate authorities, as supported by
    +  OpenSSL. (Issue #701)
    +
    +* New exception: ``NewConnectionError``, raised when we fail to establish
    +  a new connection, usually ``ECONNREFUSED`` socket error.
    +
    +
    +1.11 (2015-07-21)
    +-----------------
    +
    +* When ``ca_certs`` is given, ``cert_reqs`` defaults to
    +  ``'CERT_REQUIRED'``. (Issue #650)
    +
    +* ``pip install urllib3[secure]`` will install Certifi and
    +  PyOpenSSL as dependencies. (Issue #678)
    +
    +* Made ``HTTPHeaderDict`` usable as a ``headers`` input value
    +  (Issues #632, #679)
    +
    +* Added `urllib3.contrib.appengine `_
    +  which has an ``AppEngineManager`` for using ``URLFetch`` in a
    +  Google AppEngine environment. (Issue #664)
    +
    +* Dev: Added test suite for AppEngine. (Issue #631)
    +
    +* Fix performance regression when using PyOpenSSL. (Issue #626)
    +
    +* Passing incorrect scheme (e.g. ``foo://``) will raise
    +  ``ValueError`` instead of ``AssertionError`` (backwards
    +  compatible for now, but please migrate). (Issue #640)
    +
    +* Fix pools not getting replenished when an error occurs during a
    +  request using ``release_conn=False``. (Issue #644)
    +
    +* Fix pool-default headers not applying for url-encoded requests
    +  like GET. (Issue #657)
    +
    +* log.warning in Python 3 when headers are skipped due to parsing
    +  errors. (Issue #642)
    +
    +* Close and discard connections if an error occurs during read.
    +  (Issue #660)
    +
    +* Fix host parsing for IPv6 proxies. (Issue #668)
    +
    +* Separate warning type SubjectAltNameWarning, now issued once
    +  per host. (Issue #671)
    +
    +* Fix ``httplib.IncompleteRead`` not getting converted to
    +  ``ProtocolError`` when using ``HTTPResponse.stream()``
    +  (Issue #674)
    +
    +1.10.4 (2015-05-03)
    +-------------------
    +
    +* Migrate tests to Tornado 4. (Issue #594)
    +
    +* Append default warning configuration rather than overwrite.
    +  (Issue #603)
    +
    +* Fix streaming decoding regression. (Issue #595)
    +
    +* Fix chunked requests losing state across keep-alive connections.
    +  (Issue #599)
    +
    +* Fix hanging when chunked HEAD response has no body. (Issue #605)
    +
    +
    +1.10.3 (2015-04-21)
    +-------------------
    +
    +* Emit ``InsecurePlatformWarning`` when SSLContext object is missing.
    +  (Issue #558)
    +
    +* Fix regression of duplicate header keys being discarded.
    +  (Issue #563)
    +
    +* ``Response.stream()`` returns a generator for chunked responses.
    +  (Issue #560)
    +
    +* Set upper-bound timeout when waiting for a socket in PyOpenSSL.
    +  (Issue #585)
    +
    +* Work on platforms without `ssl` module for plain HTTP requests.
    +  (Issue #587)
    +
    +* Stop relying on the stdlib's default cipher list. (Issue #588)
    +
    +
    +1.10.2 (2015-02-25)
    +-------------------
    +
    +* Fix file descriptor leakage on retries. (Issue #548)
    +
    +* Removed RC4 from default cipher list. (Issue #551)
    +
    +* Header performance improvements. (Issue #544)
    +
    +* Fix PoolManager not obeying redirect retry settings. (Issue #553)
    +
    +
    +1.10.1 (2015-02-10)
    +-------------------
    +
    +* Pools can be used as context managers. (Issue #545)
    +
    +* Don't re-use connections which experienced an SSLError. (Issue #529)
    +
    +* Don't fail when gzip decoding an empty stream. (Issue #535)
    +
    +* Add sha256 support for fingerprint verification. (Issue #540)
    +
    +* Fixed handling of header values containing commas. (Issue #533)
    +
    +
    +1.10 (2014-12-14)
    +-----------------
    +
    +* Disabled SSLv3. (Issue #473)
    +
    +* Add ``Url.url`` property to return the composed url string. (Issue #394)
    +
    +* Fixed PyOpenSSL + gevent ``WantWriteError``. (Issue #412)
    +
    +* ``MaxRetryError.reason`` will always be an exception, not string.
    +  (Issue #481)
    +
    +* Fixed SSL-related timeouts not being detected as timeouts. (Issue #492)
    +
    +* Py3: Use ``ssl.create_default_context()`` when available. (Issue #473)
    +
    +* Emit ``InsecureRequestWarning`` for *every* insecure HTTPS request.
    +  (Issue #496)
    +
    +* Emit ``SecurityWarning`` when certificate has no ``subjectAltName``.
    +  (Issue #499)
    +
    +* Close and discard sockets which experienced SSL-related errors.
    +  (Issue #501)
    +
    +* Handle ``body`` param in ``.request(...)``. (Issue #513)
    +
    +* Respect timeout with HTTPS proxy. (Issue #505)
    +
    +* PyOpenSSL: Handle ZeroReturnError exception. (Issue #520)
    +
    +
    +1.9.1 (2014-09-13)
    +------------------
    +
    +* Apply socket arguments before binding. (Issue #427)
    +
    +* More careful checks if fp-like object is closed. (Issue #435)
    +
    +* Fixed packaging issues of some development-related files not
    +  getting included. (Issue #440)
    +
    +* Allow performing *only* fingerprint verification. (Issue #444)
    +
    +* Emit ``SecurityWarning`` if system clock is waaay off. (Issue #445)
    +
    +* Fixed PyOpenSSL compatibility with PyPy. (Issue #450)
    +
    +* Fixed ``BrokenPipeError`` and ``ConnectionError`` handling in Py3.
    +  (Issue #443)
    +
    +
    +
    +1.9 (2014-07-04)
    +----------------
    +
    +* Shuffled around development-related files. If you're maintaining a distro
    +  package of urllib3, you may need to tweak things. (Issue #415)
    +
    +* Unverified HTTPS requests will trigger a warning on the first request. See
    +  our new `security documentation
    +  `_ for details.
    +  (Issue #426)
    +
    +* New retry logic and ``urllib3.util.retry.Retry`` configuration object.
    +  (Issue #326)
    +
    +* All raised exceptions should now wrapped in a
    +  ``urllib3.exceptions.HTTPException``-extending exception. (Issue #326)
    +
    +* All errors during a retry-enabled request should be wrapped in
    +  ``urllib3.exceptions.MaxRetryError``, including timeout-related exceptions
    +  which were previously exempt. Underlying error is accessible from the
    +  ``.reason`` property. (Issue #326)
    +
    +* ``urllib3.exceptions.ConnectionError`` renamed to
    +  ``urllib3.exceptions.ProtocolError``. (Issue #326)
    +
    +* Errors during response read (such as IncompleteRead) are now wrapped in
    +  ``urllib3.exceptions.ProtocolError``. (Issue #418)
    +
    +* Requesting an empty host will raise ``urllib3.exceptions.LocationValueError``.
    +  (Issue #417)
    +
    +* Catch read timeouts over SSL connections as
    +  ``urllib3.exceptions.ReadTimeoutError``. (Issue #419)
    +
    +* Apply socket arguments before connecting. (Issue #427)
    +
    +
    +1.8.3 (2014-06-23)
    +------------------
    +
    +* Fix TLS verification when using a proxy in Python 3.4.1. (Issue #385)
    +
    +* Add ``disable_cache`` option to ``urllib3.util.make_headers``. (Issue #393)
    +
    +* Wrap ``socket.timeout`` exception with
    +  ``urllib3.exceptions.ReadTimeoutError``. (Issue #399)
    +
    +* Fixed proxy-related bug where connections were being reused incorrectly.
    +  (Issues #366, #369)
    +
    +* Added ``socket_options`` keyword parameter which allows to define
    +  ``setsockopt`` configuration of new sockets. (Issue #397)
    +
    +* Removed ``HTTPConnection.tcp_nodelay`` in favor of
    +  ``HTTPConnection.default_socket_options``. (Issue #397)
    +
    +* Fixed ``TypeError`` bug in Python 2.6.4. (Issue #411)
    +
    +
    +1.8.2 (2014-04-17)
    +------------------
    +
    +* Fix ``urllib3.util`` not being included in the package.
    +
    +
    +1.8.1 (2014-04-17)
    +------------------
    +
    +* Fix AppEngine bug of HTTPS requests going out as HTTP. (Issue #356)
    +
    +* Don't install ``dummyserver`` into ``site-packages`` as it's only needed
    +  for the test suite. (Issue #362)
    +
    +* Added support for specifying ``source_address``. (Issue #352)
    +
    +
    +1.8 (2014-03-04)
    +----------------
    +
    +* Improved url parsing in ``urllib3.util.parse_url`` (properly parse '@' in
    +  username, and blank ports like 'hostname:').
    +
    +* New ``urllib3.connection`` module which contains all the HTTPConnection
    +  objects.
    +
    +* Several ``urllib3.util.Timeout``-related fixes. Also changed constructor
    +  signature to a more sensible order. [Backwards incompatible]
    +  (Issues #252, #262, #263)
    +
    +* Use ``backports.ssl_match_hostname`` if it's installed. (Issue #274)
    +
    +* Added ``.tell()`` method to ``urllib3.response.HTTPResponse`` which
    +  returns the number of bytes read so far. (Issue #277)
    +
    +* Support for platforms without threading. (Issue #289)
    +
    +* Expand default-port comparison in ``HTTPConnectionPool.is_same_host``
    +  to allow a pool with no specified port to be considered equal to to an
    +  HTTP/HTTPS url with port 80/443 explicitly provided. (Issue #305)
    +
    +* Improved default SSL/TLS settings to avoid vulnerabilities.
    +  (Issue #309)
    +
    +* Fixed ``urllib3.poolmanager.ProxyManager`` not retrying on connect errors.
    +  (Issue #310)
    +
    +* Disable Nagle's Algorithm on the socket for non-proxies. A subset of requests
    +  will send the entire HTTP request ~200 milliseconds faster; however, some of
    +  the resulting TCP packets will be smaller. (Issue #254)
    +
    +* Increased maximum number of SubjectAltNames in ``urllib3.contrib.pyopenssl``
    +  from the default 64 to 1024 in a single certificate. (Issue #318)
    +
    +* Headers are now passed and stored as a custom
    +  ``urllib3.collections_.HTTPHeaderDict`` object rather than a plain ``dict``.
    +  (Issue #329, #333)
    +
    +* Headers no longer lose their case on Python 3. (Issue #236)
    +
    +* ``urllib3.contrib.pyopenssl`` now uses the operating system's default CA
    +  certificates on inject. (Issue #332)
    +
    +* Requests with ``retries=False`` will immediately raise any exceptions without
    +  wrapping them in ``MaxRetryError``. (Issue #348)
    +
    +* Fixed open socket leak with SSL-related failures. (Issue #344, #348)
    +
    +
    +1.7.1 (2013-09-25)
    +------------------
    +
    +* Added granular timeout support with new ``urllib3.util.Timeout`` class.
    +  (Issue #231)
    +
    +* Fixed Python 3.4 support. (Issue #238)
    +
    +
    +1.7 (2013-08-14)
    +----------------
    +
    +* More exceptions are now pickle-able, with tests. (Issue #174)
    +
    +* Fixed redirecting with relative URLs in Location header. (Issue #178)
    +
    +* Support for relative urls in ``Location: ...`` header. (Issue #179)
    +
    +* ``urllib3.response.HTTPResponse`` now inherits from ``io.IOBase`` for bonus
    +  file-like functionality. (Issue #187)
    +
    +* Passing ``assert_hostname=False`` when creating a HTTPSConnectionPool will
    +  skip hostname verification for SSL connections. (Issue #194)
    +
    +* New method ``urllib3.response.HTTPResponse.stream(...)`` which acts as a
    +  generator wrapped around ``.read(...)``. (Issue #198)
    +
    +* IPv6 url parsing enforces brackets around the hostname. (Issue #199)
    +
    +* Fixed thread race condition in
    +  ``urllib3.poolmanager.PoolManager.connection_from_host(...)`` (Issue #204)
    +
    +* ``ProxyManager`` requests now include non-default port in ``Host: ...``
    +  header. (Issue #217)
    +
    +* Added HTTPS proxy support in ``ProxyManager``. (Issue #170 #139)
    +
    +* New ``RequestField`` object can be passed to the ``fields=...`` param which
    +  can specify headers. (Issue #220)
    +
    +* Raise ``urllib3.exceptions.ProxyError`` when connecting to proxy fails.
    +  (Issue #221)
    +
    +* Use international headers when posting file names. (Issue #119)
    +
    +* Improved IPv6 support. (Issue #203)
    +
    +
    +1.6 (2013-04-25)
    +----------------
    +
    +* Contrib: Optional SNI support for Py2 using PyOpenSSL. (Issue #156)
    +
    +* ``ProxyManager`` automatically adds ``Host: ...`` header if not given.
    +
    +* Improved SSL-related code. ``cert_req`` now optionally takes a string like
    +  "REQUIRED" or "NONE". Same with ``ssl_version`` takes strings like "SSLv23"
    +  The string values reflect the suffix of the respective constant variable.
    +  (Issue #130)
    +
    +* Vendored ``socksipy`` now based on Anorov's fork which handles unexpectedly
    +  closed proxy connections and larger read buffers. (Issue #135)
    +
    +* Ensure the connection is closed if no data is received, fixes connection leak
    +  on some platforms. (Issue #133)
    +
    +* Added SNI support for SSL/TLS connections on Py32+. (Issue #89)
    +
    +* Tests fixed to be compatible with Py26 again. (Issue #125)
    +
    +* Added ability to choose SSL version by passing an ``ssl.PROTOCOL_*`` constant
    +  to the ``ssl_version`` parameter of ``HTTPSConnectionPool``. (Issue #109)
    +
    +* Allow an explicit content type to be specified when encoding file fields.
    +  (Issue #126)
    +
    +* Exceptions are now pickleable, with tests. (Issue #101)
    +
    +* Fixed default headers not getting passed in some cases. (Issue #99)
    +
    +* Treat "content-encoding" header value as case-insensitive, per RFC 2616
    +  Section 3.5. (Issue #110)
    +
    +* "Connection Refused" SocketErrors will get retried rather than raised.
    +  (Issue #92)
    +
    +* Updated vendored ``six``, no longer overrides the global ``six`` module
    +  namespace. (Issue #113)
    +
    +* ``urllib3.exceptions.MaxRetryError`` contains a ``reason`` property holding
    +  the exception that prompted the final retry. If ``reason is None`` then it
    +  was due to a redirect. (Issue #92, #114)
    +
    +* Fixed ``PoolManager.urlopen()`` from not redirecting more than once.
    +  (Issue #149)
    +
    +* Don't assume ``Content-Type: text/plain`` for multi-part encoding parameters
    +  that are not files. (Issue #111)
    +
    +* Pass `strict` param down to ``httplib.HTTPConnection``. (Issue #122)
    +
    +* Added mechanism to verify SSL certificates by fingerprint (md5, sha1) or
    +  against an arbitrary hostname (when connecting by IP or for misconfigured
    +  servers). (Issue #140)
    +
    +* Streaming decompression support. (Issue #159)
    +
    +
    +1.5 (2012-08-02)
    +----------------
    +
    +* Added ``urllib3.add_stderr_logger()`` for quickly enabling STDERR debug
    +  logging in urllib3.
    +
    +* Native full URL parsing (including auth, path, query, fragment) available in
    +  ``urllib3.util.parse_url(url)``.
    +
    +* Built-in redirect will switch method to 'GET' if status code is 303.
    +  (Issue #11)
    +
    +* ``urllib3.PoolManager`` strips the scheme and host before sending the request
    +  uri. (Issue #8)
    +
    +* New ``urllib3.exceptions.DecodeError`` exception for when automatic decoding,
    +  based on the Content-Type header, fails.
    +
    +* Fixed bug with pool depletion and leaking connections (Issue #76). Added
    +  explicit connection closing on pool eviction. Added
    +  ``urllib3.PoolManager.clear()``.
    +
    +* 99% -> 100% unit test coverage.
    +
    +
    +1.4 (2012-06-16)
    +----------------
    +
    +* Minor AppEngine-related fixes.
    +
    +* Switched from ``mimetools.choose_boundary`` to ``uuid.uuid4()``.
    +
    +* Improved url parsing. (Issue #73)
    +
    +* IPv6 url support. (Issue #72)
    +
    +
    +1.3 (2012-03-25)
    +----------------
    +
    +* Removed pre-1.0 deprecated API.
    +
    +* Refactored helpers into a ``urllib3.util`` submodule.
    +
    +* Fixed multipart encoding to support list-of-tuples for keys with multiple
    +  values. (Issue #48)
    +
    +* Fixed multiple Set-Cookie headers in response not getting merged properly in
    +  Python 3. (Issue #53)
    +
    +* AppEngine support with Py27. (Issue #61)
    +
    +* Minor ``encode_multipart_formdata`` fixes related to Python 3 strings vs
    +  bytes.
    +
    +
    +1.2.2 (2012-02-06)
    +------------------
    +
    +* Fixed packaging bug of not shipping ``test-requirements.txt``. (Issue #47)
    +
    +
    +1.2.1 (2012-02-05)
    +------------------
    +
    +* Fixed another bug related to when ``ssl`` module is not available. (Issue #41)
    +
    +* Location parsing errors now raise ``urllib3.exceptions.LocationParseError``
    +  which inherits from ``ValueError``.
    +
    +
    +1.2 (2012-01-29)
    +----------------
    +
    +* Added Python 3 support (tested on 3.2.2)
    +
    +* Dropped Python 2.5 support (tested on 2.6.7, 2.7.2)
    +
    +* Use ``select.poll`` instead of ``select.select`` for platforms that support
    +  it.
    +
    +* Use ``Queue.LifoQueue`` instead of ``Queue.Queue`` for more aggressive
    +  connection reusing. Configurable by overriding ``ConnectionPool.QueueCls``.
    +
    +* Fixed ``ImportError`` during install when ``ssl`` module is not available.
    +  (Issue #41)
    +
    +* Fixed ``PoolManager`` redirects between schemes (such as HTTP -> HTTPS) not
    +  completing properly. (Issue #28, uncovered by Issue #10 in v1.1)
    +
    +* Ported ``dummyserver`` to use ``tornado`` instead of ``webob`` +
    +  ``eventlet``. Removed extraneous unsupported dummyserver testing backends.
    +  Added socket-level tests.
    +
    +* More tests. Achievement Unlocked: 99% Coverage.
    +
    +
    +1.1 (2012-01-07)
    +----------------
    +
    +* Refactored ``dummyserver`` to its own root namespace module (used for
    +  testing).
    +
    +* Added hostname verification for ``VerifiedHTTPSConnection`` by vendoring in
    +  Py32's ``ssl_match_hostname``. (Issue #25)
    +
    +* Fixed cross-host HTTP redirects when using ``PoolManager``. (Issue #10)
    +
    +* Fixed ``decode_content`` being ignored when set through ``urlopen``. (Issue
    +  #27)
    +
    +* Fixed timeout-related bugs. (Issues #17, #23)
    +
    +
    +1.0.2 (2011-11-04)
    +------------------
    +
    +* Fixed typo in ``VerifiedHTTPSConnection`` which would only present as a bug if
    +  you're using the object manually. (Thanks pyos)
    +
    +* Made RecentlyUsedContainer (and consequently PoolManager) more thread-safe by
    +  wrapping the access log in a mutex. (Thanks @christer)
    +
    +* Made RecentlyUsedContainer more dict-like (corrected ``__delitem__`` and
    +  ``__getitem__`` behaviour), with tests. Shouldn't affect core urllib3 code.
    +
    +
    +1.0.1 (2011-10-10)
    +------------------
    +
    +* Fixed a bug where the same connection would get returned into the pool twice,
    +  causing extraneous "HttpConnectionPool is full" log warnings.
    +
    +
    +1.0 (2011-10-08)
    +----------------
    +
    +* Added ``PoolManager`` with LRU expiration of connections (tested and
    +  documented).
    +* Added ``ProxyManager`` (needs tests, docs, and confirmation that it works
    +  with HTTPS proxies).
    +* Added optional partial-read support for responses when
    +  ``preload_content=False``. You can now make requests and just read the headers
    +  without loading the content.
    +* Made response decoding optional (default on, same as before).
    +* Added optional explicit boundary string for ``encode_multipart_formdata``.
    +* Convenience request methods are now inherited from ``RequestMethods``. Old
    +  helpers like ``get_url`` and ``post_url`` should be abandoned in favour of
    +  the new ``request(method, url, ...)``.
    +* Refactored code to be even more decoupled, reusable, and extendable.
    +* License header added to ``.py`` files.
    +* Embiggened the documentation: Lots of Sphinx-friendly docstrings in the code
    +  and docs in ``docs/`` and on https://urllib3.readthedocs.io/.
    +* Embettered all the things!
    +* Started writing this file.
    +
    +
    +0.4.1 (2011-07-17)
    +------------------
    +
    +* Minor bug fixes, code cleanup.
    +
    +
    +0.4 (2011-03-01)
    +----------------
    +
    +* Better unicode support.
    +* Added ``VerifiedHTTPSConnection``.
    +* Added ``NTLMConnectionPool`` in contrib.
    +* Minor improvements.
    +
    +
    +0.3.1 (2010-07-13)
    +------------------
    +
    +* Added ``assert_host_name`` optional parameter. Now compatible with proxies.
    +
    +
    +0.3 (2009-12-10)
    +----------------
    +
    +* Added HTTPS support.
    +* Minor bug fixes.
    +* Refactored, broken backwards compatibility with 0.2.
    +* API to be treated as stable from this version forward.
    +
    +
    +0.2 (2008-11-17)
    +----------------
    +
    +* Added unit tests.
    +* Bug fixes.
    +
    +
    +0.1 (2008-11-16)
    +----------------
    +
    +* First release.
    +
    +
    diff --git a/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/RECORD b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/RECORD
    new file mode 100644
    index 0000000..4fb24fc
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/RECORD
    @@ -0,0 +1,78 @@
    +urllib3-1.25.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
    +urllib3-1.25.7.dist-info/LICENSE.txt,sha256=fA0TbuBYU4mt8tJWcbuZaHofdZKfRlt_Fu4_Ado3JV4,1115
    +urllib3-1.25.7.dist-info/METADATA,sha256=9YvStEd7yBTKiX8vv2nhP5io758PIFay1wO4lZwo0Bo,38768
    +urllib3-1.25.7.dist-info/RECORD,,
    +urllib3-1.25.7.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
    +urllib3-1.25.7.dist-info/top_level.txt,sha256=EMiXL2sKrTcmrMxIHTqdc3ET54pQI2Y072LexFEemvo,8
    +urllib3/__init__.py,sha256=--dxP-3k5qC8gGCQJbU_jJK666_rbCduadrwRB25wZg,2683
    +urllib3/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/__pycache__/_collections.cpython-37.pyc,,
    +urllib3/__pycache__/connection.cpython-37.pyc,,
    +urllib3/__pycache__/connectionpool.cpython-37.pyc,,
    +urllib3/__pycache__/exceptions.cpython-37.pyc,,
    +urllib3/__pycache__/fields.cpython-37.pyc,,
    +urllib3/__pycache__/filepost.cpython-37.pyc,,
    +urllib3/__pycache__/poolmanager.cpython-37.pyc,,
    +urllib3/__pycache__/request.cpython-37.pyc,,
    +urllib3/__pycache__/response.cpython-37.pyc,,
    +urllib3/_collections.py,sha256=GouVsNzwg6jADZTmimMI6oqmwKSswnMo9dh5tGNVWO4,10792
    +urllib3/connection.py,sha256=JaGozqRdvNogTwHDGxbp2N3Hi2MtJQrkbr7b5qcBGXk,15168
    +urllib3/connectionpool.py,sha256=2RPMZJU_PhkAbY1tvy3-W_9os4Kdk_XXu8Zi6YSCgSU,36488
    +urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
    +urllib3/contrib/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/contrib/__pycache__/_appengine_environ.cpython-37.pyc,,
    +urllib3/contrib/__pycache__/appengine.cpython-37.pyc,,
    +urllib3/contrib/__pycache__/ntlmpool.cpython-37.pyc,,
    +urllib3/contrib/__pycache__/pyopenssl.cpython-37.pyc,,
    +urllib3/contrib/__pycache__/securetransport.cpython-37.pyc,,
    +urllib3/contrib/__pycache__/socks.cpython-37.pyc,,
    +urllib3/contrib/_appengine_environ.py,sha256=PCxFG7RoB-AOkIWQWGBIg1yZnK0dwPxWcNx7BTpZFBI,909
    +urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
    +urllib3/contrib/_securetransport/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/contrib/_securetransport/__pycache__/bindings.cpython-37.pyc,,
    +urllib3/contrib/_securetransport/__pycache__/low_level.cpython-37.pyc,,
    +urllib3/contrib/_securetransport/bindings.py,sha256=mullWYFaghBdRWla6HYU-TBgFRTPLBEfxj3jplbeJmQ,16886
    +urllib3/contrib/_securetransport/low_level.py,sha256=V7GnujxnWZh2N2sMsV5N4d9Imymokkm3zBwgt77_bSE,11956
    +urllib3/contrib/appengine.py,sha256=9RyUW5vKy4VPa2imtwBNWYKILrypr-K6UXEHUYsf0JY,11010
    +urllib3/contrib/ntlmpool.py,sha256=a402AwGN_Ll3N-4ur_AS6UrU-ycUtlnYqoBF76lORg8,4160
    +urllib3/contrib/pyopenssl.py,sha256=Lp95UgpAQsI5-zTGy42-oxU_2n6XyneC7gNHw3qx0Cw,16423
    +urllib3/contrib/securetransport.py,sha256=iKzVUAxKnChsADR5YMwc05oEixXDzAk0xPU0g-rc2z8,32275
    +urllib3/contrib/socks.py,sha256=nzDMgDIFJWVubKHqvIn2-SKCO91hhJInP92WgHChGzA,7036
    +urllib3/exceptions.py,sha256=P3e-p9_LScyIxX7FoR3wU0A6hZmDqFAVCz2wgI3D0lM,6607
    +urllib3/fields.py,sha256=kroD76QK-GdHHW7f_AUN4XxDC3OQPI2FFrS9eSL4BCs,8553
    +urllib3/filepost.py,sha256=vj0qbrpT1AFzvvW4SuC8M5kJiw7wftHcSr-7b8UpPpw,2440
    +urllib3/packages/__init__.py,sha256=h4BLhD4tLaBx1adaDtKXfupsgqY0wWLXb_f1_yVlV6A,108
    +urllib3/packages/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/packages/__pycache__/six.cpython-37.pyc,,
    +urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
    +urllib3/packages/backports/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/packages/backports/__pycache__/makefile.cpython-37.pyc,,
    +urllib3/packages/backports/makefile.py,sha256=005wrvH-_pWSnTFqQ2sdzzh4zVCtQUUQ4mR2Yyxwc0A,1418
    +urllib3/packages/six.py,sha256=adx4z-eM_D0Vvu0IIqVzFACQ_ux9l64y7DkSEfbxCDs,32536
    +urllib3/packages/ssl_match_hostname/__init__.py,sha256=ywgKMtfHi1-DrXlzPfVAhzsLzzqcK7GT6eLgdode1Fg,688
    +urllib3/packages/ssl_match_hostname/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/packages/ssl_match_hostname/__pycache__/_implementation.cpython-37.pyc,,
    +urllib3/packages/ssl_match_hostname/_implementation.py,sha256=6dZ-q074g7XhsJ27MFCgkct8iVNZB3sMZvKhf-KUVy0,5679
    +urllib3/poolmanager.py,sha256=JYUyBUN3IiEknUdjZ7VJrpCQr6SP7vi0WwSndrn8XpE,17053
    +urllib3/request.py,sha256=hhoHvEEatyd9Tn5EbGjQ0emn-ENMCyY591yNWTneINA,6018
    +urllib3/response.py,sha256=O2DVzBeWOzyxZDZ8k0EDFU3GW1jWXk_b03mS0O1ybxs,27836
    +urllib3/util/__init__.py,sha256=bWNaav_OT-1L7-sxm59cGb59rDORlbhb_4noduM5m0U,1038
    +urllib3/util/__pycache__/__init__.cpython-37.pyc,,
    +urllib3/util/__pycache__/connection.cpython-37.pyc,,
    +urllib3/util/__pycache__/queue.cpython-37.pyc,,
    +urllib3/util/__pycache__/request.cpython-37.pyc,,
    +urllib3/util/__pycache__/response.cpython-37.pyc,,
    +urllib3/util/__pycache__/retry.cpython-37.pyc,,
    +urllib3/util/__pycache__/ssl_.cpython-37.pyc,,
    +urllib3/util/__pycache__/timeout.cpython-37.pyc,,
    +urllib3/util/__pycache__/url.cpython-37.pyc,,
    +urllib3/util/__pycache__/wait.cpython-37.pyc,,
    +urllib3/util/connection.py,sha256=NsxUAKQ98GKywta--zg57CdVpeTCI6N-GElCq78Dl8U,4637
    +urllib3/util/queue.py,sha256=myTX3JDHntglKQNBf3b6dasHH-uF-W59vzGSQiFdAfI,497
    +urllib3/util/request.py,sha256=C-6-AWffxZG03AdRGoY59uqsn4CVItKU6gjxz7Hc3Mc,3815
    +urllib3/util/response.py,sha256=_WbTQr8xRQuJuY2rTIZxVdJD6mnEOtQupjaK_bF_Vj8,2573
    +urllib3/util/retry.py,sha256=Ui74h44gLIIWkAxT9SK3A2mEvu55-odWgJMw3LiUNGk,15450
    +urllib3/util/ssl_.py,sha256=engc91JD3PYAEPdoqdDnMnWtUT549Ma2nNTWJiAWRtc,14151
    +urllib3/util/timeout.py,sha256=bCtaS_xVKaTDJ5VMlroXBfCnPUDNVGZqik7-z83issg,9871
    +urllib3/util/url.py,sha256=P0jDIHCnMJb4x1qILZYnn3hkcxOSWYrDtvzjiC_d218,14096
    +urllib3/util/wait.py,sha256=k46KzqIYu3Vnzla5YW3EvtInNlU_QycFqQAghIOxoAg,5406
    diff --git a/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/WHEEL b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/WHEEL
    new file mode 100644
    index 0000000..8b701e9
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/WHEEL
    @@ -0,0 +1,6 @@
    +Wheel-Version: 1.0
    +Generator: bdist_wheel (0.33.6)
    +Root-Is-Purelib: true
    +Tag: py2-none-any
    +Tag: py3-none-any
    +
    diff --git a/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/top_level.txt b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/top_level.txt
    new file mode 100644
    index 0000000..a42590b
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3-1.25.7.dist-info/top_level.txt
    @@ -0,0 +1 @@
    +urllib3
    diff --git a/venv/lib/python3.7/site-packages/urllib3/__init__.py b/venv/lib/python3.7/site-packages/urllib3/__init__.py
    new file mode 100644
    index 0000000..96474d3
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/__init__.py
    @@ -0,0 +1,86 @@
    +"""
    +urllib3 - Thread-safe connection pooling and re-using.
    +"""
    +from __future__ import absolute_import
    +import warnings
    +
    +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url
    +
    +from . import exceptions
    +from .filepost import encode_multipart_formdata
    +from .poolmanager import PoolManager, ProxyManager, proxy_from_url
    +from .response import HTTPResponse
    +from .util.request import make_headers
    +from .util.url import get_host
    +from .util.timeout import Timeout
    +from .util.retry import Retry
    +
    +
    +# Set default logging handler to avoid "No handler found" warnings.
    +import logging
    +from logging import NullHandler
    +
    +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)"
    +__license__ = "MIT"
    +__version__ = "1.25.7"
    +
    +__all__ = (
    +    "HTTPConnectionPool",
    +    "HTTPSConnectionPool",
    +    "PoolManager",
    +    "ProxyManager",
    +    "HTTPResponse",
    +    "Retry",
    +    "Timeout",
    +    "add_stderr_logger",
    +    "connection_from_url",
    +    "disable_warnings",
    +    "encode_multipart_formdata",
    +    "get_host",
    +    "make_headers",
    +    "proxy_from_url",
    +)
    +
    +logging.getLogger(__name__).addHandler(NullHandler())
    +
    +
    +def add_stderr_logger(level=logging.DEBUG):
    +    """
    +    Helper for quickly adding a StreamHandler to the logger. Useful for
    +    debugging.
    +
    +    Returns the handler after adding it.
    +    """
    +    # This method needs to be in this __init__.py to get the __name__ correct
    +    # even if urllib3 is vendored within another package.
    +    logger = logging.getLogger(__name__)
    +    handler = logging.StreamHandler()
    +    handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
    +    logger.addHandler(handler)
    +    logger.setLevel(level)
    +    logger.debug("Added a stderr logging handler to logger: %s", __name__)
    +    return handler
    +
    +
    +# ... Clean up.
    +del NullHandler
    +
    +
    +# All warning filters *must* be appended unless you're really certain that they
    +# shouldn't be: otherwise, it's very hard for users to use most Python
    +# mechanisms to silence them.
    +# SecurityWarning's always go off by default.
    +warnings.simplefilter("always", exceptions.SecurityWarning, append=True)
    +# SubjectAltNameWarning's should go off once per host
    +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True)
    +# InsecurePlatformWarning's don't vary between requests, so we keep it default.
    +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True)
    +# SNIMissingWarnings should go off only once.
    +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True)
    +
    +
    +def disable_warnings(category=exceptions.HTTPWarning):
    +    """
    +    Helper for quickly disabling all urllib3 warnings.
    +    """
    +    warnings.simplefilter("ignore", category)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/_collections.py b/venv/lib/python3.7/site-packages/urllib3/_collections.py
    new file mode 100644
    index 0000000..019d151
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/_collections.py
    @@ -0,0 +1,336 @@
    +from __future__ import absolute_import
    +
    +try:
    +    from collections.abc import Mapping, MutableMapping
    +except ImportError:
    +    from collections import Mapping, MutableMapping
    +try:
    +    from threading import RLock
    +except ImportError:  # Platform-specific: No threads available
    +
    +    class RLock:
    +        def __enter__(self):
    +            pass
    +
    +        def __exit__(self, exc_type, exc_value, traceback):
    +            pass
    +
    +
    +from collections import OrderedDict
    +from .exceptions import InvalidHeader
    +from .packages.six import iterkeys, itervalues, PY3
    +
    +
    +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"]
    +
    +
    +_Null = object()
    +
    +
    +class RecentlyUsedContainer(MutableMapping):
    +    """
    +    Provides a thread-safe dict-like container which maintains up to
    +    ``maxsize`` keys while throwing away the least-recently-used keys beyond
    +    ``maxsize``.
    +
    +    :param maxsize:
    +        Maximum number of recent elements to retain.
    +
    +    :param dispose_func:
    +        Every time an item is evicted from the container,
    +        ``dispose_func(value)`` is called.  Callback which will get called
    +    """
    +
    +    ContainerCls = OrderedDict
    +
    +    def __init__(self, maxsize=10, dispose_func=None):
    +        self._maxsize = maxsize
    +        self.dispose_func = dispose_func
    +
    +        self._container = self.ContainerCls()
    +        self.lock = RLock()
    +
    +    def __getitem__(self, key):
    +        # Re-insert the item, moving it to the end of the eviction line.
    +        with self.lock:
    +            item = self._container.pop(key)
    +            self._container[key] = item
    +            return item
    +
    +    def __setitem__(self, key, value):
    +        evicted_value = _Null
    +        with self.lock:
    +            # Possibly evict the existing value of 'key'
    +            evicted_value = self._container.get(key, _Null)
    +            self._container[key] = value
    +
    +            # If we didn't evict an existing value, we might have to evict the
    +            # least recently used item from the beginning of the container.
    +            if len(self._container) > self._maxsize:
    +                _key, evicted_value = self._container.popitem(last=False)
    +
    +        if self.dispose_func and evicted_value is not _Null:
    +            self.dispose_func(evicted_value)
    +
    +    def __delitem__(self, key):
    +        with self.lock:
    +            value = self._container.pop(key)
    +
    +        if self.dispose_func:
    +            self.dispose_func(value)
    +
    +    def __len__(self):
    +        with self.lock:
    +            return len(self._container)
    +
    +    def __iter__(self):
    +        raise NotImplementedError(
    +            "Iteration over this class is unlikely to be threadsafe."
    +        )
    +
    +    def clear(self):
    +        with self.lock:
    +            # Copy pointers to all values, then wipe the mapping
    +            values = list(itervalues(self._container))
    +            self._container.clear()
    +
    +        if self.dispose_func:
    +            for value in values:
    +                self.dispose_func(value)
    +
    +    def keys(self):
    +        with self.lock:
    +            return list(iterkeys(self._container))
    +
    +
    +class HTTPHeaderDict(MutableMapping):
    +    """
    +    :param headers:
    +        An iterable of field-value pairs. Must not contain multiple field names
    +        when compared case-insensitively.
    +
    +    :param kwargs:
    +        Additional field-value pairs to pass in to ``dict.update``.
    +
    +    A ``dict`` like container for storing HTTP Headers.
    +
    +    Field names are stored and compared case-insensitively in compliance with
    +    RFC 7230. Iteration provides the first case-sensitive key seen for each
    +    case-insensitive pair.
    +
    +    Using ``__setitem__`` syntax overwrites fields that compare equal
    +    case-insensitively in order to maintain ``dict``'s api. For fields that
    +    compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
    +    in a loop.
    +
    +    If multiple fields that are equal case-insensitively are passed to the
    +    constructor or ``.update``, the behavior is undefined and some will be
    +    lost.
    +
    +    >>> headers = HTTPHeaderDict()
    +    >>> headers.add('Set-Cookie', 'foo=bar')
    +    >>> headers.add('set-cookie', 'baz=quxx')
    +    >>> headers['content-length'] = '7'
    +    >>> headers['SET-cookie']
    +    'foo=bar, baz=quxx'
    +    >>> headers['Content-Length']
    +    '7'
    +    """
    +
    +    def __init__(self, headers=None, **kwargs):
    +        super(HTTPHeaderDict, self).__init__()
    +        self._container = OrderedDict()
    +        if headers is not None:
    +            if isinstance(headers, HTTPHeaderDict):
    +                self._copy_from(headers)
    +            else:
    +                self.extend(headers)
    +        if kwargs:
    +            self.extend(kwargs)
    +
    +    def __setitem__(self, key, val):
    +        self._container[key.lower()] = [key, val]
    +        return self._container[key.lower()]
    +
    +    def __getitem__(self, key):
    +        val = self._container[key.lower()]
    +        return ", ".join(val[1:])
    +
    +    def __delitem__(self, key):
    +        del self._container[key.lower()]
    +
    +    def __contains__(self, key):
    +        return key.lower() in self._container
    +
    +    def __eq__(self, other):
    +        if not isinstance(other, Mapping) and not hasattr(other, "keys"):
    +            return False
    +        if not isinstance(other, type(self)):
    +            other = type(self)(other)
    +        return dict((k.lower(), v) for k, v in self.itermerged()) == dict(
    +            (k.lower(), v) for k, v in other.itermerged()
    +        )
    +
    +    def __ne__(self, other):
    +        return not self.__eq__(other)
    +
    +    if not PY3:  # Python 2
    +        iterkeys = MutableMapping.iterkeys
    +        itervalues = MutableMapping.itervalues
    +
    +    __marker = object()
    +
    +    def __len__(self):
    +        return len(self._container)
    +
    +    def __iter__(self):
    +        # Only provide the originally cased names
    +        for vals in self._container.values():
    +            yield vals[0]
    +
    +    def pop(self, key, default=__marker):
    +        """D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
    +          If key is not found, d is returned if given, otherwise KeyError is raised.
    +        """
    +        # Using the MutableMapping function directly fails due to the private marker.
    +        # Using ordinary dict.pop would expose the internal structures.
    +        # So let's reinvent the wheel.
    +        try:
    +            value = self[key]
    +        except KeyError:
    +            if default is self.__marker:
    +                raise
    +            return default
    +        else:
    +            del self[key]
    +            return value
    +
    +    def discard(self, key):
    +        try:
    +            del self[key]
    +        except KeyError:
    +            pass
    +
    +    def add(self, key, val):
    +        """Adds a (name, value) pair, doesn't overwrite the value if it already
    +        exists.
    +
    +        >>> headers = HTTPHeaderDict(foo='bar')
    +        >>> headers.add('Foo', 'baz')
    +        >>> headers['foo']
    +        'bar, baz'
    +        """
    +        key_lower = key.lower()
    +        new_vals = [key, val]
    +        # Keep the common case aka no item present as fast as possible
    +        vals = self._container.setdefault(key_lower, new_vals)
    +        if new_vals is not vals:
    +            vals.append(val)
    +
    +    def extend(self, *args, **kwargs):
    +        """Generic import function for any type of header-like object.
    +        Adapted version of MutableMapping.update in order to insert items
    +        with self.add instead of self.__setitem__
    +        """
    +        if len(args) > 1:
    +            raise TypeError(
    +                "extend() takes at most 1 positional "
    +                "arguments ({0} given)".format(len(args))
    +            )
    +        other = args[0] if len(args) >= 1 else ()
    +
    +        if isinstance(other, HTTPHeaderDict):
    +            for key, val in other.iteritems():
    +                self.add(key, val)
    +        elif isinstance(other, Mapping):
    +            for key in other:
    +                self.add(key, other[key])
    +        elif hasattr(other, "keys"):
    +            for key in other.keys():
    +                self.add(key, other[key])
    +        else:
    +            for key, value in other:
    +                self.add(key, value)
    +
    +        for key, value in kwargs.items():
    +            self.add(key, value)
    +
    +    def getlist(self, key, default=__marker):
    +        """Returns a list of all the values for the named field. Returns an
    +        empty list if the key doesn't exist."""
    +        try:
    +            vals = self._container[key.lower()]
    +        except KeyError:
    +            if default is self.__marker:
    +                return []
    +            return default
    +        else:
    +            return vals[1:]
    +
    +    # Backwards compatibility for httplib
    +    getheaders = getlist
    +    getallmatchingheaders = getlist
    +    iget = getlist
    +
    +    # Backwards compatibility for http.cookiejar
    +    get_all = getlist
    +
    +    def __repr__(self):
    +        return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
    +
    +    def _copy_from(self, other):
    +        for key in other:
    +            val = other.getlist(key)
    +            if isinstance(val, list):
    +                # Don't need to convert tuples
    +                val = list(val)
    +            self._container[key.lower()] = [key] + val
    +
    +    def copy(self):
    +        clone = type(self)()
    +        clone._copy_from(self)
    +        return clone
    +
    +    def iteritems(self):
    +        """Iterate over all header lines, including duplicate ones."""
    +        for key in self:
    +            vals = self._container[key.lower()]
    +            for val in vals[1:]:
    +                yield vals[0], val
    +
    +    def itermerged(self):
    +        """Iterate over all headers, merging duplicate ones together."""
    +        for key in self:
    +            val = self._container[key.lower()]
    +            yield val[0], ", ".join(val[1:])
    +
    +    def items(self):
    +        return list(self.iteritems())
    +
    +    @classmethod
    +    def from_httplib(cls, message):  # Python 2
    +        """Read headers from a Python 2 httplib message object."""
    +        # python2.7 does not expose a proper API for exporting multiheaders
    +        # efficiently. This function re-reads raw lines from the message
    +        # object and extracts the multiheaders properly.
    +        obs_fold_continued_leaders = (" ", "\t")
    +        headers = []
    +
    +        for line in message.headers:
    +            if line.startswith(obs_fold_continued_leaders):
    +                if not headers:
    +                    # We received a header line that starts with OWS as described
    +                    # in RFC-7230 S3.2.4. This indicates a multiline header, but
    +                    # there exists no previous header to which we can attach it.
    +                    raise InvalidHeader(
    +                        "Header continuation with no previous header: %s" % line
    +                    )
    +                else:
    +                    key, value = headers[-1]
    +                    headers[-1] = (key, value + " " + line.strip())
    +                    continue
    +
    +            key, value = line.split(":", 1)
    +            headers.append((key, value.strip()))
    +
    +        return cls(headers)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/connection.py b/venv/lib/python3.7/site-packages/urllib3/connection.py
    new file mode 100644
    index 0000000..f5c946a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/connection.py
    @@ -0,0 +1,448 @@
    +from __future__ import absolute_import
    +import datetime
    +import logging
    +import os
    +import socket
    +from socket import error as SocketError, timeout as SocketTimeout
    +import warnings
    +from .packages import six
    +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection
    +from .packages.six.moves.http_client import HTTPException  # noqa: F401
    +
    +try:  # Compiled with SSL?
    +    import ssl
    +
    +    BaseSSLError = ssl.SSLError
    +except (ImportError, AttributeError):  # Platform-specific: No SSL.
    +    ssl = None
    +
    +    class BaseSSLError(BaseException):
    +        pass
    +
    +
    +try:
    +    # Python 3: not a no-op, we're adding this to the namespace so it can be imported.
    +    ConnectionError = ConnectionError
    +except NameError:
    +    # Python 2
    +    class ConnectionError(Exception):
    +        pass
    +
    +
    +from .exceptions import (
    +    NewConnectionError,
    +    ConnectTimeoutError,
    +    SubjectAltNameWarning,
    +    SystemTimeWarning,
    +)
    +from .packages.ssl_match_hostname import match_hostname, CertificateError
    +
    +from .util.ssl_ import (
    +    resolve_cert_reqs,
    +    resolve_ssl_version,
    +    assert_fingerprint,
    +    create_urllib3_context,
    +    ssl_wrap_socket,
    +)
    +
    +
    +from .util import connection
    +
    +from ._collections import HTTPHeaderDict
    +
    +log = logging.getLogger(__name__)
    +
    +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(2019, 1, 1)
    +
    +
    +class DummyConnection(object):
    +    """Used to detect a failed ConnectionCls import."""
    +
    +    pass
    +
    +
    +class HTTPConnection(_HTTPConnection, object):
    +    """
    +    Based on httplib.HTTPConnection but provides an extra constructor
    +    backwards-compatibility layer between older and newer Pythons.
    +
    +    Additional keyword parameters are used to configure attributes of the connection.
    +    Accepted parameters include:
    +
    +      - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool`
    +      - ``source_address``: Set the source address for the current connection.
    +      - ``socket_options``: Set specific options on the underlying socket. If not specified, then
    +        defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling
    +        Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy.
    +
    +        For example, if you wish to enable TCP Keep Alive in addition to the defaults,
    +        you might pass::
    +
    +            HTTPConnection.default_socket_options + [
    +                (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
    +            ]
    +
    +        Or you may want to disable the defaults by passing an empty list (e.g., ``[]``).
    +    """
    +
    +    default_port = port_by_scheme["http"]
    +
    +    #: Disable Nagle's algorithm by default.
    +    #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]``
    +    default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
    +
    +    #: Whether this connection verifies the host's certificate.
    +    is_verified = False
    +
    +    def __init__(self, *args, **kw):
    +        if not six.PY2:
    +            kw.pop("strict", None)
    +
    +        # Pre-set source_address.
    +        self.source_address = kw.get("source_address")
    +
    +        #: The socket options provided by the user. If no options are
    +        #: provided, we use the default options.
    +        self.socket_options = kw.pop("socket_options", self.default_socket_options)
    +
    +        _HTTPConnection.__init__(self, *args, **kw)
    +
    +    @property
    +    def host(self):
    +        """
    +        Getter method to remove any trailing dots that indicate the hostname is an FQDN.
    +
    +        In general, SSL certificates don't include the trailing dot indicating a
    +        fully-qualified domain name, and thus, they don't validate properly when
    +        checked against a domain name that includes the dot. In addition, some
    +        servers may not expect to receive the trailing dot when provided.
    +
    +        However, the hostname with trailing dot is critical to DNS resolution; doing a
    +        lookup with the trailing dot will properly only resolve the appropriate FQDN,
    +        whereas a lookup without a trailing dot will search the system's search domain
    +        list. Thus, it's important to keep the original host around for use only in
    +        those cases where it's appropriate (i.e., when doing DNS lookup to establish the
    +        actual TCP connection across which we're going to send HTTP requests).
    +        """
    +        return self._dns_host.rstrip(".")
    +
    +    @host.setter
    +    def host(self, value):
    +        """
    +        Setter for the `host` property.
    +
    +        We assume that only urllib3 uses the _dns_host attribute; httplib itself
    +        only uses `host`, and it seems reasonable that other libraries follow suit.
    +        """
    +        self._dns_host = value
    +
    +    def _new_conn(self):
    +        """ Establish a socket connection and set nodelay settings on it.
    +
    +        :return: New socket connection.
    +        """
    +        extra_kw = {}
    +        if self.source_address:
    +            extra_kw["source_address"] = self.source_address
    +
    +        if self.socket_options:
    +            extra_kw["socket_options"] = self.socket_options
    +
    +        try:
    +            conn = connection.create_connection(
    +                (self._dns_host, self.port), self.timeout, **extra_kw
    +            )
    +
    +        except SocketTimeout:
    +            raise ConnectTimeoutError(
    +                self,
    +                "Connection to %s timed out. (connect timeout=%s)"
    +                % (self.host, self.timeout),
    +            )
    +
    +        except SocketError as e:
    +            raise NewConnectionError(
    +                self, "Failed to establish a new connection: %s" % e
    +            )
    +
    +        return conn
    +
    +    def _prepare_conn(self, conn):
    +        self.sock = conn
    +        # Google App Engine's httplib does not define _tunnel_host
    +        if getattr(self, "_tunnel_host", None):
    +            # TODO: Fix tunnel so it doesn't depend on self.sock state.
    +            self._tunnel()
    +            # Mark this connection as not reusable
    +            self.auto_open = 0
    +
    +    def connect(self):
    +        conn = self._new_conn()
    +        self._prepare_conn(conn)
    +
    +    def request_chunked(self, method, url, body=None, headers=None):
    +        """
    +        Alternative to the common request method, which sends the
    +        body with chunked encoding and not as one block
    +        """
    +        headers = HTTPHeaderDict(headers if headers is not None else {})
    +        skip_accept_encoding = "accept-encoding" in headers
    +        skip_host = "host" in headers
    +        self.putrequest(
    +            method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host
    +        )
    +        for header, value in headers.items():
    +            self.putheader(header, value)
    +        if "transfer-encoding" not in headers:
    +            self.putheader("Transfer-Encoding", "chunked")
    +        self.endheaders()
    +
    +        if body is not None:
    +            stringish_types = six.string_types + (bytes,)
    +            if isinstance(body, stringish_types):
    +                body = (body,)
    +            for chunk in body:
    +                if not chunk:
    +                    continue
    +                if not isinstance(chunk, bytes):
    +                    chunk = chunk.encode("utf8")
    +                len_str = hex(len(chunk))[2:]
    +                self.send(len_str.encode("utf-8"))
    +                self.send(b"\r\n")
    +                self.send(chunk)
    +                self.send(b"\r\n")
    +
    +        # After the if clause, to always have a closed body
    +        self.send(b"0\r\n\r\n")
    +
    +
    +class HTTPSConnection(HTTPConnection):
    +    default_port = port_by_scheme["https"]
    +
    +    ssl_version = None
    +
    +    def __init__(
    +        self,
    +        host,
    +        port=None,
    +        key_file=None,
    +        cert_file=None,
    +        key_password=None,
    +        strict=None,
    +        timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
    +        ssl_context=None,
    +        server_hostname=None,
    +        **kw
    +    ):
    +
    +        HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw)
    +
    +        self.key_file = key_file
    +        self.cert_file = cert_file
    +        self.key_password = key_password
    +        self.ssl_context = ssl_context
    +        self.server_hostname = server_hostname
    +
    +        # Required property for Google AppEngine 1.9.0 which otherwise causes
    +        # HTTPS requests to go out as HTTP. (See Issue #356)
    +        self._protocol = "https"
    +
    +    def connect(self):
    +        conn = self._new_conn()
    +        self._prepare_conn(conn)
    +
    +        # Wrap socket using verification with the root certs in
    +        # trusted_root_certs
    +        default_ssl_context = False
    +        if self.ssl_context is None:
    +            default_ssl_context = True
    +            self.ssl_context = create_urllib3_context(
    +                ssl_version=resolve_ssl_version(self.ssl_version),
    +                cert_reqs=resolve_cert_reqs(self.cert_reqs),
    +            )
    +
    +        # Try to load OS default certs if none are given.
    +        # Works well on Windows (requires Python3.4+)
    +        context = self.ssl_context
    +        if (
    +            not self.ca_certs
    +            and not self.ca_cert_dir
    +            and default_ssl_context
    +            and hasattr(context, "load_default_certs")
    +        ):
    +            context.load_default_certs()
    +
    +        self.sock = ssl_wrap_socket(
    +            sock=conn,
    +            keyfile=self.key_file,
    +            certfile=self.cert_file,
    +            key_password=self.key_password,
    +            ssl_context=self.ssl_context,
    +            server_hostname=self.server_hostname,
    +        )
    +
    +
    +class VerifiedHTTPSConnection(HTTPSConnection):
    +    """
    +    Based on httplib.HTTPSConnection but wraps the socket with
    +    SSL certification.
    +    """
    +
    +    cert_reqs = None
    +    ca_certs = None
    +    ca_cert_dir = None
    +    ssl_version = None
    +    assert_fingerprint = None
    +
    +    def set_cert(
    +        self,
    +        key_file=None,
    +        cert_file=None,
    +        cert_reqs=None,
    +        key_password=None,
    +        ca_certs=None,
    +        assert_hostname=None,
    +        assert_fingerprint=None,
    +        ca_cert_dir=None,
    +    ):
    +        """
    +        This method should only be called once, before the connection is used.
    +        """
    +        # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also
    +        # have an SSLContext object in which case we'll use its verify_mode.
    +        if cert_reqs is None:
    +            if self.ssl_context is not None:
    +                cert_reqs = self.ssl_context.verify_mode
    +            else:
    +                cert_reqs = resolve_cert_reqs(None)
    +
    +        self.key_file = key_file
    +        self.cert_file = cert_file
    +        self.cert_reqs = cert_reqs
    +        self.key_password = key_password
    +        self.assert_hostname = assert_hostname
    +        self.assert_fingerprint = assert_fingerprint
    +        self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
    +        self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
    +
    +    def connect(self):
    +        # Add certificate verification
    +        conn = self._new_conn()
    +        hostname = self.host
    +
    +        # Google App Engine's httplib does not define _tunnel_host
    +        if getattr(self, "_tunnel_host", None):
    +            self.sock = conn
    +            # Calls self._set_hostport(), so self.host is
    +            # self._tunnel_host below.
    +            self._tunnel()
    +            # Mark this connection as not reusable
    +            self.auto_open = 0
    +
    +            # Override the host with the one we're requesting data from.
    +            hostname = self._tunnel_host
    +
    +        server_hostname = hostname
    +        if self.server_hostname is not None:
    +            server_hostname = self.server_hostname
    +
    +        is_time_off = datetime.date.today() < RECENT_DATE
    +        if is_time_off:
    +            warnings.warn(
    +                (
    +                    "System time is way off (before {0}). This will probably "
    +                    "lead to SSL verification errors"
    +                ).format(RECENT_DATE),
    +                SystemTimeWarning,
    +            )
    +
    +        # Wrap socket using verification with the root certs in
    +        # trusted_root_certs
    +        default_ssl_context = False
    +        if self.ssl_context is None:
    +            default_ssl_context = True
    +            self.ssl_context = create_urllib3_context(
    +                ssl_version=resolve_ssl_version(self.ssl_version),
    +                cert_reqs=resolve_cert_reqs(self.cert_reqs),
    +            )
    +
    +        context = self.ssl_context
    +        context.verify_mode = resolve_cert_reqs(self.cert_reqs)
    +
    +        # Try to load OS default certs if none are given.
    +        # Works well on Windows (requires Python3.4+)
    +        if (
    +            not self.ca_certs
    +            and not self.ca_cert_dir
    +            and default_ssl_context
    +            and hasattr(context, "load_default_certs")
    +        ):
    +            context.load_default_certs()
    +
    +        self.sock = ssl_wrap_socket(
    +            sock=conn,
    +            keyfile=self.key_file,
    +            certfile=self.cert_file,
    +            key_password=self.key_password,
    +            ca_certs=self.ca_certs,
    +            ca_cert_dir=self.ca_cert_dir,
    +            server_hostname=server_hostname,
    +            ssl_context=context,
    +        )
    +
    +        if self.assert_fingerprint:
    +            assert_fingerprint(
    +                self.sock.getpeercert(binary_form=True), self.assert_fingerprint
    +            )
    +        elif (
    +            context.verify_mode != ssl.CERT_NONE
    +            and not getattr(context, "check_hostname", False)
    +            and self.assert_hostname is not False
    +        ):
    +            # While urllib3 attempts to always turn off hostname matching from
    +            # the TLS library, this cannot always be done. So we check whether
    +            # the TLS Library still thinks it's matching hostnames.
    +            cert = self.sock.getpeercert()
    +            if not cert.get("subjectAltName", ()):
    +                warnings.warn(
    +                    (
    +                        "Certificate for {0} has no `subjectAltName`, falling back to check for a "
    +                        "`commonName` for now. This feature is being removed by major browsers and "
    +                        "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 "
    +                        "for details.)".format(hostname)
    +                    ),
    +                    SubjectAltNameWarning,
    +                )
    +            _match_hostname(cert, self.assert_hostname or server_hostname)
    +
    +        self.is_verified = (
    +            context.verify_mode == ssl.CERT_REQUIRED
    +            or self.assert_fingerprint is not None
    +        )
    +
    +
    +def _match_hostname(cert, asserted_hostname):
    +    try:
    +        match_hostname(cert, asserted_hostname)
    +    except CertificateError as e:
    +        log.warning(
    +            "Certificate did not match expected hostname: %s. Certificate: %s",
    +            asserted_hostname,
    +            cert,
    +        )
    +        # Add cert to exception and reraise so client code can inspect
    +        # the cert when catching the exception, if they want to
    +        e._peer_cert = cert
    +        raise
    +
    +
    +if ssl:
    +    # Make a copy for testing.
    +    UnverifiedHTTPSConnection = HTTPSConnection
    +    HTTPSConnection = VerifiedHTTPSConnection
    +else:
    +    HTTPSConnection = DummyConnection
    diff --git a/venv/lib/python3.7/site-packages/urllib3/connectionpool.py b/venv/lib/python3.7/site-packages/urllib3/connectionpool.py
    new file mode 100644
    index 0000000..3169646
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/connectionpool.py
    @@ -0,0 +1,1051 @@
    +from __future__ import absolute_import
    +import errno
    +import logging
    +import sys
    +import warnings
    +
    +from socket import error as SocketError, timeout as SocketTimeout
    +import socket
    +
    +
    +from .exceptions import (
    +    ClosedPoolError,
    +    ProtocolError,
    +    EmptyPoolError,
    +    HeaderParsingError,
    +    HostChangedError,
    +    LocationValueError,
    +    MaxRetryError,
    +    ProxyError,
    +    ReadTimeoutError,
    +    SSLError,
    +    TimeoutError,
    +    InsecureRequestWarning,
    +    NewConnectionError,
    +)
    +from .packages.ssl_match_hostname import CertificateError
    +from .packages import six
    +from .packages.six.moves import queue
    +from .connection import (
    +    port_by_scheme,
    +    DummyConnection,
    +    HTTPConnection,
    +    HTTPSConnection,
    +    VerifiedHTTPSConnection,
    +    HTTPException,
    +    BaseSSLError,
    +)
    +from .request import RequestMethods
    +from .response import HTTPResponse
    +
    +from .util.connection import is_connection_dropped
    +from .util.request import set_file_position
    +from .util.response import assert_header_parsing
    +from .util.retry import Retry
    +from .util.timeout import Timeout
    +from .util.url import (
    +    get_host,
    +    parse_url,
    +    Url,
    +    _normalize_host as normalize_host,
    +    _encode_target,
    +)
    +from .util.queue import LifoQueue
    +
    +
    +xrange = six.moves.xrange
    +
    +log = logging.getLogger(__name__)
    +
    +_Default = object()
    +
    +
    +# Pool objects
    +class ConnectionPool(object):
    +    """
    +    Base class for all connection pools, such as
    +    :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.
    +    """
    +
    +    scheme = None
    +    QueueCls = LifoQueue
    +
    +    def __init__(self, host, port=None):
    +        if not host:
    +            raise LocationValueError("No host specified.")
    +
    +        self.host = _normalize_host(host, scheme=self.scheme)
    +        self._proxy_host = host.lower()
    +        self.port = port
    +
    +    def __str__(self):
    +        return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port)
    +
    +    def __enter__(self):
    +        return self
    +
    +    def __exit__(self, exc_type, exc_val, exc_tb):
    +        self.close()
    +        # Return False to re-raise any potential exceptions
    +        return False
    +
    +    def close(self):
    +        """
    +        Close all pooled connections and disable the pool.
    +        """
    +        pass
    +
    +
    +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
    +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK}
    +
    +
    +class HTTPConnectionPool(ConnectionPool, RequestMethods):
    +    """
    +    Thread-safe connection pool for one host.
    +
    +    :param host:
    +        Host used for this HTTP Connection (e.g. "localhost"), passed into
    +        :class:`httplib.HTTPConnection`.
    +
    +    :param port:
    +        Port used for this HTTP Connection (None is equivalent to 80), passed
    +        into :class:`httplib.HTTPConnection`.
    +
    +    :param strict:
    +        Causes BadStatusLine to be raised if the status line can't be parsed
    +        as a valid HTTP/1.0 or 1.1 status line, passed into
    +        :class:`httplib.HTTPConnection`.
    +
    +        .. note::
    +           Only works in Python 2. This parameter is ignored in Python 3.
    +
    +    :param timeout:
    +        Socket timeout in seconds for each individual connection. This can
    +        be a float or integer, which sets the timeout for the HTTP request,
    +        or an instance of :class:`urllib3.util.Timeout` which gives you more
    +        fine-grained control over request timeouts. After the constructor has
    +        been parsed, this is always a `urllib3.util.Timeout` object.
    +
    +    :param maxsize:
    +        Number of connections to save that can be reused. More than 1 is useful
    +        in multithreaded situations. If ``block`` is set to False, more
    +        connections will be created but they will not be saved once they've
    +        been used.
    +
    +    :param block:
    +        If set to True, no more than ``maxsize`` connections will be used at
    +        a time. When no free connections are available, the call will block
    +        until a connection has been released. This is a useful side effect for
    +        particular multithreaded situations where one does not want to use more
    +        than maxsize connections per host to prevent flooding.
    +
    +    :param headers:
    +        Headers to include with all requests, unless other headers are given
    +        explicitly.
    +
    +    :param retries:
    +        Retry configuration to use by default with requests in this pool.
    +
    +    :param _proxy:
    +        Parsed proxy URL, should not be used directly, instead, see
    +        :class:`urllib3.connectionpool.ProxyManager`"
    +
    +    :param _proxy_headers:
    +        A dictionary with proxy headers, should not be used directly,
    +        instead, see :class:`urllib3.connectionpool.ProxyManager`"
    +
    +    :param \\**conn_kw:
    +        Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`,
    +        :class:`urllib3.connection.HTTPSConnection` instances.
    +    """
    +
    +    scheme = "http"
    +    ConnectionCls = HTTPConnection
    +    ResponseCls = HTTPResponse
    +
    +    def __init__(
    +        self,
    +        host,
    +        port=None,
    +        strict=False,
    +        timeout=Timeout.DEFAULT_TIMEOUT,
    +        maxsize=1,
    +        block=False,
    +        headers=None,
    +        retries=None,
    +        _proxy=None,
    +        _proxy_headers=None,
    +        **conn_kw
    +    ):
    +        ConnectionPool.__init__(self, host, port)
    +        RequestMethods.__init__(self, headers)
    +
    +        self.strict = strict
    +
    +        if not isinstance(timeout, Timeout):
    +            timeout = Timeout.from_float(timeout)
    +
    +        if retries is None:
    +            retries = Retry.DEFAULT
    +
    +        self.timeout = timeout
    +        self.retries = retries
    +
    +        self.pool = self.QueueCls(maxsize)
    +        self.block = block
    +
    +        self.proxy = _proxy
    +        self.proxy_headers = _proxy_headers or {}
    +
    +        # Fill the queue up so that doing get() on it will block properly
    +        for _ in xrange(maxsize):
    +            self.pool.put(None)
    +
    +        # These are mostly for testing and debugging purposes.
    +        self.num_connections = 0
    +        self.num_requests = 0
    +        self.conn_kw = conn_kw
    +
    +        if self.proxy:
    +            # Enable Nagle's algorithm for proxies, to avoid packet fragmentation.
    +            # We cannot know if the user has added default socket options, so we cannot replace the
    +            # list.
    +            self.conn_kw.setdefault("socket_options", [])
    +
    +    def _new_conn(self):
    +        """
    +        Return a fresh :class:`HTTPConnection`.
    +        """
    +        self.num_connections += 1
    +        log.debug(
    +            "Starting new HTTP connection (%d): %s:%s",
    +            self.num_connections,
    +            self.host,
    +            self.port or "80",
    +        )
    +
    +        conn = self.ConnectionCls(
    +            host=self.host,
    +            port=self.port,
    +            timeout=self.timeout.connect_timeout,
    +            strict=self.strict,
    +            **self.conn_kw
    +        )
    +        return conn
    +
    +    def _get_conn(self, timeout=None):
    +        """
    +        Get a connection. Will return a pooled connection if one is available.
    +
    +        If no connections are available and :prop:`.block` is ``False``, then a
    +        fresh connection is returned.
    +
    +        :param timeout:
    +            Seconds to wait before giving up and raising
    +            :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and
    +            :prop:`.block` is ``True``.
    +        """
    +        conn = None
    +        try:
    +            conn = self.pool.get(block=self.block, timeout=timeout)
    +
    +        except AttributeError:  # self.pool is None
    +            raise ClosedPoolError(self, "Pool is closed.")
    +
    +        except queue.Empty:
    +            if self.block:
    +                raise EmptyPoolError(
    +                    self,
    +                    "Pool reached maximum size and no more connections are allowed.",
    +                )
    +            pass  # Oh well, we'll create a new connection then
    +
    +        # If this is a persistent connection, check if it got disconnected
    +        if conn and is_connection_dropped(conn):
    +            log.debug("Resetting dropped connection: %s", self.host)
    +            conn.close()
    +            if getattr(conn, "auto_open", 1) == 0:
    +                # This is a proxied connection that has been mutated by
    +                # httplib._tunnel() and cannot be reused (since it would
    +                # attempt to bypass the proxy)
    +                conn = None
    +
    +        return conn or self._new_conn()
    +
    +    def _put_conn(self, conn):
    +        """
    +        Put a connection back into the pool.
    +
    +        :param conn:
    +            Connection object for the current host and port as returned by
    +            :meth:`._new_conn` or :meth:`._get_conn`.
    +
    +        If the pool is already full, the connection is closed and discarded
    +        because we exceeded maxsize. If connections are discarded frequently,
    +        then maxsize should be increased.
    +
    +        If the pool is closed, then the connection will be closed and discarded.
    +        """
    +        try:
    +            self.pool.put(conn, block=False)
    +            return  # Everything is dandy, done.
    +        except AttributeError:
    +            # self.pool is None.
    +            pass
    +        except queue.Full:
    +            # This should never happen if self.block == True
    +            log.warning("Connection pool is full, discarding connection: %s", self.host)
    +
    +        # Connection never got put back into the pool, close it.
    +        if conn:
    +            conn.close()
    +
    +    def _validate_conn(self, conn):
    +        """
    +        Called right before a request is made, after the socket is created.
    +        """
    +        pass
    +
    +    def _prepare_proxy(self, conn):
    +        # Nothing to do for HTTP connections.
    +        pass
    +
    +    def _get_timeout(self, timeout):
    +        """ Helper that always returns a :class:`urllib3.util.Timeout` """
    +        if timeout is _Default:
    +            return self.timeout.clone()
    +
    +        if isinstance(timeout, Timeout):
    +            return timeout.clone()
    +        else:
    +            # User passed us an int/float. This is for backwards compatibility,
    +            # can be removed later
    +            return Timeout.from_float(timeout)
    +
    +    def _raise_timeout(self, err, url, timeout_value):
    +        """Is the error actually a timeout? Will raise a ReadTimeout or pass"""
    +
    +        if isinstance(err, SocketTimeout):
    +            raise ReadTimeoutError(
    +                self, url, "Read timed out. (read timeout=%s)" % timeout_value
    +            )
    +
    +        # See the above comment about EAGAIN in Python 3. In Python 2 we have
    +        # to specifically catch it and throw the timeout error
    +        if hasattr(err, "errno") and err.errno in _blocking_errnos:
    +            raise ReadTimeoutError(
    +                self, url, "Read timed out. (read timeout=%s)" % timeout_value
    +            )
    +
    +        # Catch possible read timeouts thrown as SSL errors. If not the
    +        # case, rethrow the original. We need to do this because of:
    +        # http://bugs.python.org/issue10272
    +        if "timed out" in str(err) or "did not complete (read)" in str(
    +            err
    +        ):  # Python < 2.7.4
    +            raise ReadTimeoutError(
    +                self, url, "Read timed out. (read timeout=%s)" % timeout_value
    +            )
    +
    +    def _make_request(
    +        self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw
    +    ):
    +        """
    +        Perform a request on a given urllib connection object taken from our
    +        pool.
    +
    +        :param conn:
    +            a connection from one of our connection pools
    +
    +        :param timeout:
    +            Socket timeout in seconds for the request. This can be a
    +            float or integer, which will set the same timeout value for
    +            the socket connect and the socket read, or an instance of
    +            :class:`urllib3.util.Timeout`, which gives you more fine-grained
    +            control over your timeouts.
    +        """
    +        self.num_requests += 1
    +
    +        timeout_obj = self._get_timeout(timeout)
    +        timeout_obj.start_connect()
    +        conn.timeout = timeout_obj.connect_timeout
    +
    +        # Trigger any extra validation we need to do.
    +        try:
    +            self._validate_conn(conn)
    +        except (SocketTimeout, BaseSSLError) as e:
    +            # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.
    +            self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
    +            raise
    +
    +        # conn.request() calls httplib.*.request, not the method in
    +        # urllib3.request. It also calls makefile (recv) on the socket.
    +        if chunked:
    +            conn.request_chunked(method, url, **httplib_request_kw)
    +        else:
    +            conn.request(method, url, **httplib_request_kw)
    +
    +        # Reset the timeout for the recv() on the socket
    +        read_timeout = timeout_obj.read_timeout
    +
    +        # App Engine doesn't have a sock attr
    +        if getattr(conn, "sock", None):
    +            # In Python 3 socket.py will catch EAGAIN and return None when you
    +            # try and read into the file pointer created by http.client, which
    +            # instead raises a BadStatusLine exception. Instead of catching
    +            # the exception and assuming all BadStatusLine exceptions are read
    +            # timeouts, check for a zero timeout before making the request.
    +            if read_timeout == 0:
    +                raise ReadTimeoutError(
    +                    self, url, "Read timed out. (read timeout=%s)" % read_timeout
    +                )
    +            if read_timeout is Timeout.DEFAULT_TIMEOUT:
    +                conn.sock.settimeout(socket.getdefaulttimeout())
    +            else:  # None or a value
    +                conn.sock.settimeout(read_timeout)
    +
    +        # Receive the response from the server
    +        try:
    +            try:
    +                # Python 2.7, use buffering of HTTP responses
    +                httplib_response = conn.getresponse(buffering=True)
    +            except TypeError:
    +                # Python 3
    +                try:
    +                    httplib_response = conn.getresponse()
    +                except BaseException as e:
    +                    # Remove the TypeError from the exception chain in
    +                    # Python 3 (including for exceptions like SystemExit).
    +                    # Otherwise it looks like a bug in the code.
    +                    six.raise_from(e, None)
    +        except (SocketTimeout, BaseSSLError, SocketError) as e:
    +            self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
    +            raise
    +
    +        # AppEngine doesn't have a version attr.
    +        http_version = getattr(conn, "_http_vsn_str", "HTTP/?")
    +        log.debug(
    +            '%s://%s:%s "%s %s %s" %s %s',
    +            self.scheme,
    +            self.host,
    +            self.port,
    +            method,
    +            url,
    +            http_version,
    +            httplib_response.status,
    +            httplib_response.length,
    +        )
    +
    +        try:
    +            assert_header_parsing(httplib_response.msg)
    +        except (HeaderParsingError, TypeError) as hpe:  # Platform-specific: Python 3
    +            log.warning(
    +                "Failed to parse headers (url=%s): %s",
    +                self._absolute_url(url),
    +                hpe,
    +                exc_info=True,
    +            )
    +
    +        return httplib_response
    +
    +    def _absolute_url(self, path):
    +        return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url
    +
    +    def close(self):
    +        """
    +        Close all pooled connections and disable the pool.
    +        """
    +        if self.pool is None:
    +            return
    +        # Disable access to the pool
    +        old_pool, self.pool = self.pool, None
    +
    +        try:
    +            while True:
    +                conn = old_pool.get(block=False)
    +                if conn:
    +                    conn.close()
    +
    +        except queue.Empty:
    +            pass  # Done.
    +
    +    def is_same_host(self, url):
    +        """
    +        Check if the given ``url`` is a member of the same host as this
    +        connection pool.
    +        """
    +        if url.startswith("/"):
    +            return True
    +
    +        # TODO: Add optional support for socket.gethostbyname checking.
    +        scheme, host, port = get_host(url)
    +        if host is not None:
    +            host = _normalize_host(host, scheme=scheme)
    +
    +        # Use explicit default port for comparison when none is given
    +        if self.port and not port:
    +            port = port_by_scheme.get(scheme)
    +        elif not self.port and port == port_by_scheme.get(scheme):
    +            port = None
    +
    +        return (scheme, host, port) == (self.scheme, self.host, self.port)
    +
    +    def urlopen(
    +        self,
    +        method,
    +        url,
    +        body=None,
    +        headers=None,
    +        retries=None,
    +        redirect=True,
    +        assert_same_host=True,
    +        timeout=_Default,
    +        pool_timeout=None,
    +        release_conn=None,
    +        chunked=False,
    +        body_pos=None,
    +        **response_kw
    +    ):
    +        """
    +        Get a connection from the pool and perform an HTTP request. This is the
    +        lowest level call for making a request, so you'll need to specify all
    +        the raw details.
    +
    +        .. note::
    +
    +           More commonly, it's appropriate to use a convenience method provided
    +           by :class:`.RequestMethods`, such as :meth:`request`.
    +
    +        .. note::
    +
    +           `release_conn` will only behave as expected if
    +           `preload_content=False` because we want to make
    +           `preload_content=False` the default behaviour someday soon without
    +           breaking backwards compatibility.
    +
    +        :param method:
    +            HTTP request method (such as GET, POST, PUT, etc.)
    +
    +        :param body:
    +            Data to send in the request body (useful for creating
    +            POST requests, see HTTPConnectionPool.post_url for
    +            more convenience).
    +
    +        :param headers:
    +            Dictionary of custom headers to send, such as User-Agent,
    +            If-None-Match, etc. If None, pool headers are used. If provided,
    +            these headers completely replace any pool-specific headers.
    +
    +        :param retries:
    +            Configure the number of retries to allow before raising a
    +            :class:`~urllib3.exceptions.MaxRetryError` exception.
    +
    +            Pass ``None`` to retry until you receive a response. Pass a
    +            :class:`~urllib3.util.retry.Retry` object for fine-grained control
    +            over different types of retries.
    +            Pass an integer number to retry connection errors that many times,
    +            but no other types of errors. Pass zero to never retry.
    +
    +            If ``False``, then retries are disabled and any exception is raised
    +            immediately. Also, instead of raising a MaxRetryError on redirects,
    +            the redirect response will be returned.
    +
    +        :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int.
    +
    +        :param redirect:
    +            If True, automatically handle redirects (status codes 301, 302,
    +            303, 307, 308). Each redirect counts as a retry. Disabling retries
    +            will disable redirect, too.
    +
    +        :param assert_same_host:
    +            If ``True``, will make sure that the host of the pool requests is
    +            consistent else will raise HostChangedError. When False, you can
    +            use the pool on an HTTP proxy and request foreign hosts.
    +
    +        :param timeout:
    +            If specified, overrides the default timeout for this one
    +            request. It may be a float (in seconds) or an instance of
    +            :class:`urllib3.util.Timeout`.
    +
    +        :param pool_timeout:
    +            If set and the pool is set to block=True, then this method will
    +            block for ``pool_timeout`` seconds and raise EmptyPoolError if no
    +            connection is available within the time period.
    +
    +        :param release_conn:
    +            If False, then the urlopen call will not release the connection
    +            back into the pool once a response is received (but will release if
    +            you read the entire contents of the response such as when
    +            `preload_content=True`). This is useful if you're not preloading
    +            the response's content immediately. You will need to call
    +            ``r.release_conn()`` on the response ``r`` to return the connection
    +            back into the pool. If None, it takes the value of
    +            ``response_kw.get('preload_content', True)``.
    +
    +        :param chunked:
    +            If True, urllib3 will send the body using chunked transfer
    +            encoding. Otherwise, urllib3 will send the body using the standard
    +            content-length form. Defaults to False.
    +
    +        :param int body_pos:
    +            Position to seek to in file-like body in the event of a retry or
    +            redirect. Typically this won't need to be set because urllib3 will
    +            auto-populate the value when needed.
    +
    +        :param \\**response_kw:
    +            Additional parameters are passed to
    +            :meth:`urllib3.response.HTTPResponse.from_httplib`
    +        """
    +        if headers is None:
    +            headers = self.headers
    +
    +        if not isinstance(retries, Retry):
    +            retries = Retry.from_int(retries, redirect=redirect, default=self.retries)
    +
    +        if release_conn is None:
    +            release_conn = response_kw.get("preload_content", True)
    +
    +        # Check host
    +        if assert_same_host and not self.is_same_host(url):
    +            raise HostChangedError(self, url, retries)
    +
    +        # Ensure that the URL we're connecting to is properly encoded
    +        if url.startswith("/"):
    +            url = six.ensure_str(_encode_target(url))
    +        else:
    +            url = six.ensure_str(parse_url(url).url)
    +
    +        conn = None
    +
    +        # Track whether `conn` needs to be released before
    +        # returning/raising/recursing. Update this variable if necessary, and
    +        # leave `release_conn` constant throughout the function. That way, if
    +        # the function recurses, the original value of `release_conn` will be
    +        # passed down into the recursive call, and its value will be respected.
    +        #
    +        # See issue #651 [1] for details.
    +        #
    +        # [1] 
    +        release_this_conn = release_conn
    +
    +        # Merge the proxy headers. Only do this in HTTP. We have to copy the
    +        # headers dict so we can safely change it without those changes being
    +        # reflected in anyone else's copy.
    +        if self.scheme == "http":
    +            headers = headers.copy()
    +            headers.update(self.proxy_headers)
    +
    +        # Must keep the exception bound to a separate variable or else Python 3
    +        # complains about UnboundLocalError.
    +        err = None
    +
    +        # Keep track of whether we cleanly exited the except block. This
    +        # ensures we do proper cleanup in finally.
    +        clean_exit = False
    +
    +        # Rewind body position, if needed. Record current position
    +        # for future rewinds in the event of a redirect/retry.
    +        body_pos = set_file_position(body, body_pos)
    +
    +        try:
    +            # Request a connection from the queue.
    +            timeout_obj = self._get_timeout(timeout)
    +            conn = self._get_conn(timeout=pool_timeout)
    +
    +            conn.timeout = timeout_obj.connect_timeout
    +
    +            is_new_proxy_conn = self.proxy is not None and not getattr(
    +                conn, "sock", None
    +            )
    +            if is_new_proxy_conn:
    +                self._prepare_proxy(conn)
    +
    +            # Make the request on the httplib connection object.
    +            httplib_response = self._make_request(
    +                conn,
    +                method,
    +                url,
    +                timeout=timeout_obj,
    +                body=body,
    +                headers=headers,
    +                chunked=chunked,
    +            )
    +
    +            # If we're going to release the connection in ``finally:``, then
    +            # the response doesn't need to know about the connection. Otherwise
    +            # it will also try to release it and we'll have a double-release
    +            # mess.
    +            response_conn = conn if not release_conn else None
    +
    +            # Pass method to Response for length checking
    +            response_kw["request_method"] = method
    +
    +            # Import httplib's response into our own wrapper object
    +            response = self.ResponseCls.from_httplib(
    +                httplib_response,
    +                pool=self,
    +                connection=response_conn,
    +                retries=retries,
    +                **response_kw
    +            )
    +
    +            # Everything went great!
    +            clean_exit = True
    +
    +        except queue.Empty:
    +            # Timed out by queue.
    +            raise EmptyPoolError(self, "No pool connections are available.")
    +
    +        except (
    +            TimeoutError,
    +            HTTPException,
    +            SocketError,
    +            ProtocolError,
    +            BaseSSLError,
    +            SSLError,
    +            CertificateError,
    +        ) as e:
    +            # Discard the connection for these exceptions. It will be
    +            # replaced during the next _get_conn() call.
    +            clean_exit = False
    +            if isinstance(e, (BaseSSLError, CertificateError)):
    +                e = SSLError(e)
    +            elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy:
    +                e = ProxyError("Cannot connect to proxy.", e)
    +            elif isinstance(e, (SocketError, HTTPException)):
    +                e = ProtocolError("Connection aborted.", e)
    +
    +            retries = retries.increment(
    +                method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
    +            )
    +            retries.sleep()
    +
    +            # Keep track of the error for the retry warning.
    +            err = e
    +
    +        finally:
    +            if not clean_exit:
    +                # We hit some kind of exception, handled or otherwise. We need
    +                # to throw the connection away unless explicitly told not to.
    +                # Close the connection, set the variable to None, and make sure
    +                # we put the None back in the pool to avoid leaking it.
    +                conn = conn and conn.close()
    +                release_this_conn = True
    +
    +            if release_this_conn:
    +                # Put the connection back to be reused. If the connection is
    +                # expired then it will be None, which will get replaced with a
    +                # fresh connection during _get_conn.
    +                self._put_conn(conn)
    +
    +        if not conn:
    +            # Try again
    +            log.warning(
    +                "Retrying (%r) after connection broken by '%r': %s", retries, err, url
    +            )
    +            return self.urlopen(
    +                method,
    +                url,
    +                body,
    +                headers,
    +                retries,
    +                redirect,
    +                assert_same_host,
    +                timeout=timeout,
    +                pool_timeout=pool_timeout,
    +                release_conn=release_conn,
    +                chunked=chunked,
    +                body_pos=body_pos,
    +                **response_kw
    +            )
    +
    +        def drain_and_release_conn(response):
    +            try:
    +                # discard any remaining response body, the connection will be
    +                # released back to the pool once the entire response is read
    +                response.read()
    +            except (
    +                TimeoutError,
    +                HTTPException,
    +                SocketError,
    +                ProtocolError,
    +                BaseSSLError,
    +                SSLError,
    +            ):
    +                pass
    +
    +        # Handle redirect?
    +        redirect_location = redirect and response.get_redirect_location()
    +        if redirect_location:
    +            if response.status == 303:
    +                method = "GET"
    +
    +            try:
    +                retries = retries.increment(method, url, response=response, _pool=self)
    +            except MaxRetryError:
    +                if retries.raise_on_redirect:
    +                    # Drain and release the connection for this response, since
    +                    # we're not returning it to be released manually.
    +                    drain_and_release_conn(response)
    +                    raise
    +                return response
    +
    +            # drain and return the connection to the pool before recursing
    +            drain_and_release_conn(response)
    +
    +            retries.sleep_for_retry(response)
    +            log.debug("Redirecting %s -> %s", url, redirect_location)
    +            return self.urlopen(
    +                method,
    +                redirect_location,
    +                body,
    +                headers,
    +                retries=retries,
    +                redirect=redirect,
    +                assert_same_host=assert_same_host,
    +                timeout=timeout,
    +                pool_timeout=pool_timeout,
    +                release_conn=release_conn,
    +                chunked=chunked,
    +                body_pos=body_pos,
    +                **response_kw
    +            )
    +
    +        # Check if we should retry the HTTP response.
    +        has_retry_after = bool(response.getheader("Retry-After"))
    +        if retries.is_retry(method, response.status, has_retry_after):
    +            try:
    +                retries = retries.increment(method, url, response=response, _pool=self)
    +            except MaxRetryError:
    +                if retries.raise_on_status:
    +                    # Drain and release the connection for this response, since
    +                    # we're not returning it to be released manually.
    +                    drain_and_release_conn(response)
    +                    raise
    +                return response
    +
    +            # drain and return the connection to the pool before recursing
    +            drain_and_release_conn(response)
    +
    +            retries.sleep(response)
    +            log.debug("Retry: %s", url)
    +            return self.urlopen(
    +                method,
    +                url,
    +                body,
    +                headers,
    +                retries=retries,
    +                redirect=redirect,
    +                assert_same_host=assert_same_host,
    +                timeout=timeout,
    +                pool_timeout=pool_timeout,
    +                release_conn=release_conn,
    +                chunked=chunked,
    +                body_pos=body_pos,
    +                **response_kw
    +            )
    +
    +        return response
    +
    +
    +class HTTPSConnectionPool(HTTPConnectionPool):
    +    """
    +    Same as :class:`.HTTPConnectionPool`, but HTTPS.
    +
    +    When Python is compiled with the :mod:`ssl` module, then
    +    :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates,
    +    instead of :class:`.HTTPSConnection`.
    +
    +    :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``,
    +    ``assert_hostname`` and ``host`` in this order to verify connections.
    +    If ``assert_hostname`` is False, no verification is done.
    +
    +    The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``,
    +    ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl`
    +    is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade
    +    the connection socket into an SSL socket.
    +    """
    +
    +    scheme = "https"
    +    ConnectionCls = HTTPSConnection
    +
    +    def __init__(
    +        self,
    +        host,
    +        port=None,
    +        strict=False,
    +        timeout=Timeout.DEFAULT_TIMEOUT,
    +        maxsize=1,
    +        block=False,
    +        headers=None,
    +        retries=None,
    +        _proxy=None,
    +        _proxy_headers=None,
    +        key_file=None,
    +        cert_file=None,
    +        cert_reqs=None,
    +        key_password=None,
    +        ca_certs=None,
    +        ssl_version=None,
    +        assert_hostname=None,
    +        assert_fingerprint=None,
    +        ca_cert_dir=None,
    +        **conn_kw
    +    ):
    +
    +        HTTPConnectionPool.__init__(
    +            self,
    +            host,
    +            port,
    +            strict,
    +            timeout,
    +            maxsize,
    +            block,
    +            headers,
    +            retries,
    +            _proxy,
    +            _proxy_headers,
    +            **conn_kw
    +        )
    +
    +        self.key_file = key_file
    +        self.cert_file = cert_file
    +        self.cert_reqs = cert_reqs
    +        self.key_password = key_password
    +        self.ca_certs = ca_certs
    +        self.ca_cert_dir = ca_cert_dir
    +        self.ssl_version = ssl_version
    +        self.assert_hostname = assert_hostname
    +        self.assert_fingerprint = assert_fingerprint
    +
    +    def _prepare_conn(self, conn):
    +        """
    +        Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket`
    +        and establish the tunnel if proxy is used.
    +        """
    +
    +        if isinstance(conn, VerifiedHTTPSConnection):
    +            conn.set_cert(
    +                key_file=self.key_file,
    +                key_password=self.key_password,
    +                cert_file=self.cert_file,
    +                cert_reqs=self.cert_reqs,
    +                ca_certs=self.ca_certs,
    +                ca_cert_dir=self.ca_cert_dir,
    +                assert_hostname=self.assert_hostname,
    +                assert_fingerprint=self.assert_fingerprint,
    +            )
    +            conn.ssl_version = self.ssl_version
    +        return conn
    +
    +    def _prepare_proxy(self, conn):
    +        """
    +        Establish tunnel connection early, because otherwise httplib
    +        would improperly set Host: header to proxy's IP:port.
    +        """
    +        conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers)
    +        conn.connect()
    +
    +    def _new_conn(self):
    +        """
    +        Return a fresh :class:`httplib.HTTPSConnection`.
    +        """
    +        self.num_connections += 1
    +        log.debug(
    +            "Starting new HTTPS connection (%d): %s:%s",
    +            self.num_connections,
    +            self.host,
    +            self.port or "443",
    +        )
    +
    +        if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
    +            raise SSLError(
    +                "Can't connect to HTTPS URL because the SSL module is not available."
    +            )
    +
    +        actual_host = self.host
    +        actual_port = self.port
    +        if self.proxy is not None:
    +            actual_host = self.proxy.host
    +            actual_port = self.proxy.port
    +
    +        conn = self.ConnectionCls(
    +            host=actual_host,
    +            port=actual_port,
    +            timeout=self.timeout.connect_timeout,
    +            strict=self.strict,
    +            cert_file=self.cert_file,
    +            key_file=self.key_file,
    +            key_password=self.key_password,
    +            **self.conn_kw
    +        )
    +
    +        return self._prepare_conn(conn)
    +
    +    def _validate_conn(self, conn):
    +        """
    +        Called right before a request is made, after the socket is created.
    +        """
    +        super(HTTPSConnectionPool, self)._validate_conn(conn)
    +
    +        # Force connect early to allow us to validate the connection.
    +        if not getattr(conn, "sock", None):  # AppEngine might not have  `.sock`
    +            conn.connect()
    +
    +        if not conn.is_verified:
    +            warnings.warn(
    +                (
    +                    "Unverified HTTPS request is being made. "
    +                    "Adding certificate verification is strongly advised. See: "
    +                    "https://urllib3.readthedocs.io/en/latest/advanced-usage.html"
    +                    "#ssl-warnings"
    +                ),
    +                InsecureRequestWarning,
    +            )
    +
    +
    +def connection_from_url(url, **kw):
    +    """
    +    Given a url, return an :class:`.ConnectionPool` instance of its host.
    +
    +    This is a shortcut for not having to parse out the scheme, host, and port
    +    of the url before creating an :class:`.ConnectionPool` instance.
    +
    +    :param url:
    +        Absolute URL string that must include the scheme. Port is optional.
    +
    +    :param \\**kw:
    +        Passes additional parameters to the constructor of the appropriate
    +        :class:`.ConnectionPool`. Useful for specifying things like
    +        timeout, maxsize, headers, etc.
    +
    +    Example::
    +
    +        >>> conn = connection_from_url('http://google.com/')
    +        >>> r = conn.request('GET', '/')
    +    """
    +    scheme, host, port = get_host(url)
    +    port = port or port_by_scheme.get(scheme, 80)
    +    if scheme == "https":
    +        return HTTPSConnectionPool(host, port=port, **kw)
    +    else:
    +        return HTTPConnectionPool(host, port=port, **kw)
    +
    +
    +def _normalize_host(host, scheme):
    +    """
    +    Normalize hosts for comparisons and use with sockets.
    +    """
    +
    +    host = normalize_host(host, scheme)
    +
    +    # httplib doesn't like it when we include brackets in IPv6 addresses
    +    # Specifically, if we include brackets but also pass the port then
    +    # httplib crazily doubles up the square brackets on the Host header.
    +    # Instead, we need to make sure we never pass ``None`` as the port.
    +    # However, for backward compatibility reasons we can't actually
    +    # *assert* that.  See http://bugs.python.org/issue28539
    +    if host.startswith("[") and host.endswith("]"):
    +        host = host[1:-1]
    +    return host
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/__init__.py b/venv/lib/python3.7/site-packages/urllib3/contrib/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/_appengine_environ.py b/venv/lib/python3.7/site-packages/urllib3/contrib/_appengine_environ.py
    new file mode 100644
    index 0000000..119efae
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/_appengine_environ.py
    @@ -0,0 +1,36 @@
    +"""
    +This module provides means to detect the App Engine environment.
    +"""
    +
    +import os
    +
    +
    +def is_appengine():
    +    return "APPENGINE_RUNTIME" in os.environ
    +
    +
    +def is_appengine_sandbox():
    +    """Reports if the app is running in the first generation sandbox.
    +
    +    The second generation runtimes are technically still in a sandbox, but it
    +    is much less restrictive, so generally you shouldn't need to check for it.
    +    see https://cloud.google.com/appengine/docs/standard/runtimes
    +    """
    +    return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27"
    +
    +
    +def is_local_appengine():
    +    return is_appengine() and os.environ.get("SERVER_SOFTWARE", "").startswith(
    +        "Development/"
    +    )
    +
    +
    +def is_prod_appengine():
    +    return is_appengine() and os.environ.get("SERVER_SOFTWARE", "").startswith(
    +        "Google App Engine/"
    +    )
    +
    +
    +def is_prod_appengine_mvms():
    +    """Deprecated."""
    +    return False
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/__init__.py b/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/bindings.py b/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/bindings.py
    new file mode 100644
    index 0000000..d9b6733
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/bindings.py
    @@ -0,0 +1,493 @@
    +"""
    +This module uses ctypes to bind a whole bunch of functions and constants from
    +SecureTransport. The goal here is to provide the low-level API to
    +SecureTransport. These are essentially the C-level functions and constants, and
    +they're pretty gross to work with.
    +
    +This code is a bastardised version of the code found in Will Bond's oscrypto
    +library. An enormous debt is owed to him for blazing this trail for us. For
    +that reason, this code should be considered to be covered both by urllib3's
    +license and by oscrypto's:
    +
    +    Copyright (c) 2015-2016 Will Bond 
    +
    +    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 absolute_import
    +
    +import platform
    +from ctypes.util import find_library
    +from ctypes import (
    +    c_void_p,
    +    c_int32,
    +    c_char_p,
    +    c_size_t,
    +    c_byte,
    +    c_uint32,
    +    c_ulong,
    +    c_long,
    +    c_bool,
    +)
    +from ctypes import CDLL, POINTER, CFUNCTYPE
    +
    +
    +security_path = find_library("Security")
    +if not security_path:
    +    raise ImportError("The library Security could not be found")
    +
    +
    +core_foundation_path = find_library("CoreFoundation")
    +if not core_foundation_path:
    +    raise ImportError("The library CoreFoundation could not be found")
    +
    +
    +version = platform.mac_ver()[0]
    +version_info = tuple(map(int, version.split(".")))
    +if version_info < (10, 8):
    +    raise OSError(
    +        "Only OS X 10.8 and newer are supported, not %s.%s"
    +        % (version_info[0], version_info[1])
    +    )
    +
    +Security = CDLL(security_path, use_errno=True)
    +CoreFoundation = CDLL(core_foundation_path, use_errno=True)
    +
    +Boolean = c_bool
    +CFIndex = c_long
    +CFStringEncoding = c_uint32
    +CFData = c_void_p
    +CFString = c_void_p
    +CFArray = c_void_p
    +CFMutableArray = c_void_p
    +CFDictionary = c_void_p
    +CFError = c_void_p
    +CFType = c_void_p
    +CFTypeID = c_ulong
    +
    +CFTypeRef = POINTER(CFType)
    +CFAllocatorRef = c_void_p
    +
    +OSStatus = c_int32
    +
    +CFDataRef = POINTER(CFData)
    +CFStringRef = POINTER(CFString)
    +CFArrayRef = POINTER(CFArray)
    +CFMutableArrayRef = POINTER(CFMutableArray)
    +CFDictionaryRef = POINTER(CFDictionary)
    +CFArrayCallBacks = c_void_p
    +CFDictionaryKeyCallBacks = c_void_p
    +CFDictionaryValueCallBacks = c_void_p
    +
    +SecCertificateRef = POINTER(c_void_p)
    +SecExternalFormat = c_uint32
    +SecExternalItemType = c_uint32
    +SecIdentityRef = POINTER(c_void_p)
    +SecItemImportExportFlags = c_uint32
    +SecItemImportExportKeyParameters = c_void_p
    +SecKeychainRef = POINTER(c_void_p)
    +SSLProtocol = c_uint32
    +SSLCipherSuite = c_uint32
    +SSLContextRef = POINTER(c_void_p)
    +SecTrustRef = POINTER(c_void_p)
    +SSLConnectionRef = c_uint32
    +SecTrustResultType = c_uint32
    +SecTrustOptionFlags = c_uint32
    +SSLProtocolSide = c_uint32
    +SSLConnectionType = c_uint32
    +SSLSessionOption = c_uint32
    +
    +
    +try:
    +    Security.SecItemImport.argtypes = [
    +        CFDataRef,
    +        CFStringRef,
    +        POINTER(SecExternalFormat),
    +        POINTER(SecExternalItemType),
    +        SecItemImportExportFlags,
    +        POINTER(SecItemImportExportKeyParameters),
    +        SecKeychainRef,
    +        POINTER(CFArrayRef),
    +    ]
    +    Security.SecItemImport.restype = OSStatus
    +
    +    Security.SecCertificateGetTypeID.argtypes = []
    +    Security.SecCertificateGetTypeID.restype = CFTypeID
    +
    +    Security.SecIdentityGetTypeID.argtypes = []
    +    Security.SecIdentityGetTypeID.restype = CFTypeID
    +
    +    Security.SecKeyGetTypeID.argtypes = []
    +    Security.SecKeyGetTypeID.restype = CFTypeID
    +
    +    Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef]
    +    Security.SecCertificateCreateWithData.restype = SecCertificateRef
    +
    +    Security.SecCertificateCopyData.argtypes = [SecCertificateRef]
    +    Security.SecCertificateCopyData.restype = CFDataRef
    +
    +    Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p]
    +    Security.SecCopyErrorMessageString.restype = CFStringRef
    +
    +    Security.SecIdentityCreateWithCertificate.argtypes = [
    +        CFTypeRef,
    +        SecCertificateRef,
    +        POINTER(SecIdentityRef),
    +    ]
    +    Security.SecIdentityCreateWithCertificate.restype = OSStatus
    +
    +    Security.SecKeychainCreate.argtypes = [
    +        c_char_p,
    +        c_uint32,
    +        c_void_p,
    +        Boolean,
    +        c_void_p,
    +        POINTER(SecKeychainRef),
    +    ]
    +    Security.SecKeychainCreate.restype = OSStatus
    +
    +    Security.SecKeychainDelete.argtypes = [SecKeychainRef]
    +    Security.SecKeychainDelete.restype = OSStatus
    +
    +    Security.SecPKCS12Import.argtypes = [
    +        CFDataRef,
    +        CFDictionaryRef,
    +        POINTER(CFArrayRef),
    +    ]
    +    Security.SecPKCS12Import.restype = OSStatus
    +
    +    SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t))
    +    SSLWriteFunc = CFUNCTYPE(
    +        OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t)
    +    )
    +
    +    Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc]
    +    Security.SSLSetIOFuncs.restype = OSStatus
    +
    +    Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t]
    +    Security.SSLSetPeerID.restype = OSStatus
    +
    +    Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef]
    +    Security.SSLSetCertificate.restype = OSStatus
    +
    +    Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean]
    +    Security.SSLSetCertificateAuthorities.restype = OSStatus
    +
    +    Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef]
    +    Security.SSLSetConnection.restype = OSStatus
    +
    +    Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t]
    +    Security.SSLSetPeerDomainName.restype = OSStatus
    +
    +    Security.SSLHandshake.argtypes = [SSLContextRef]
    +    Security.SSLHandshake.restype = OSStatus
    +
    +    Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)]
    +    Security.SSLRead.restype = OSStatus
    +
    +    Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)]
    +    Security.SSLWrite.restype = OSStatus
    +
    +    Security.SSLClose.argtypes = [SSLContextRef]
    +    Security.SSLClose.restype = OSStatus
    +
    +    Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)]
    +    Security.SSLGetNumberSupportedCiphers.restype = OSStatus
    +
    +    Security.SSLGetSupportedCiphers.argtypes = [
    +        SSLContextRef,
    +        POINTER(SSLCipherSuite),
    +        POINTER(c_size_t),
    +    ]
    +    Security.SSLGetSupportedCiphers.restype = OSStatus
    +
    +    Security.SSLSetEnabledCiphers.argtypes = [
    +        SSLContextRef,
    +        POINTER(SSLCipherSuite),
    +        c_size_t,
    +    ]
    +    Security.SSLSetEnabledCiphers.restype = OSStatus
    +
    +    Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)]
    +    Security.SSLGetNumberEnabledCiphers.restype = OSStatus
    +
    +    Security.SSLGetEnabledCiphers.argtypes = [
    +        SSLContextRef,
    +        POINTER(SSLCipherSuite),
    +        POINTER(c_size_t),
    +    ]
    +    Security.SSLGetEnabledCiphers.restype = OSStatus
    +
    +    Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)]
    +    Security.SSLGetNegotiatedCipher.restype = OSStatus
    +
    +    Security.SSLGetNegotiatedProtocolVersion.argtypes = [
    +        SSLContextRef,
    +        POINTER(SSLProtocol),
    +    ]
    +    Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus
    +
    +    Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)]
    +    Security.SSLCopyPeerTrust.restype = OSStatus
    +
    +    Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef]
    +    Security.SecTrustSetAnchorCertificates.restype = OSStatus
    +
    +    Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean]
    +    Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus
    +
    +    Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)]
    +    Security.SecTrustEvaluate.restype = OSStatus
    +
    +    Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef]
    +    Security.SecTrustGetCertificateCount.restype = CFIndex
    +
    +    Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex]
    +    Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef
    +
    +    Security.SSLCreateContext.argtypes = [
    +        CFAllocatorRef,
    +        SSLProtocolSide,
    +        SSLConnectionType,
    +    ]
    +    Security.SSLCreateContext.restype = SSLContextRef
    +
    +    Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean]
    +    Security.SSLSetSessionOption.restype = OSStatus
    +
    +    Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol]
    +    Security.SSLSetProtocolVersionMin.restype = OSStatus
    +
    +    Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol]
    +    Security.SSLSetProtocolVersionMax.restype = OSStatus
    +
    +    Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p]
    +    Security.SecCopyErrorMessageString.restype = CFStringRef
    +
    +    Security.SSLReadFunc = SSLReadFunc
    +    Security.SSLWriteFunc = SSLWriteFunc
    +    Security.SSLContextRef = SSLContextRef
    +    Security.SSLProtocol = SSLProtocol
    +    Security.SSLCipherSuite = SSLCipherSuite
    +    Security.SecIdentityRef = SecIdentityRef
    +    Security.SecKeychainRef = SecKeychainRef
    +    Security.SecTrustRef = SecTrustRef
    +    Security.SecTrustResultType = SecTrustResultType
    +    Security.SecExternalFormat = SecExternalFormat
    +    Security.OSStatus = OSStatus
    +
    +    Security.kSecImportExportPassphrase = CFStringRef.in_dll(
    +        Security, "kSecImportExportPassphrase"
    +    )
    +    Security.kSecImportItemIdentity = CFStringRef.in_dll(
    +        Security, "kSecImportItemIdentity"
    +    )
    +
    +    # CoreFoundation time!
    +    CoreFoundation.CFRetain.argtypes = [CFTypeRef]
    +    CoreFoundation.CFRetain.restype = CFTypeRef
    +
    +    CoreFoundation.CFRelease.argtypes = [CFTypeRef]
    +    CoreFoundation.CFRelease.restype = None
    +
    +    CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef]
    +    CoreFoundation.CFGetTypeID.restype = CFTypeID
    +
    +    CoreFoundation.CFStringCreateWithCString.argtypes = [
    +        CFAllocatorRef,
    +        c_char_p,
    +        CFStringEncoding,
    +    ]
    +    CoreFoundation.CFStringCreateWithCString.restype = CFStringRef
    +
    +    CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding]
    +    CoreFoundation.CFStringGetCStringPtr.restype = c_char_p
    +
    +    CoreFoundation.CFStringGetCString.argtypes = [
    +        CFStringRef,
    +        c_char_p,
    +        CFIndex,
    +        CFStringEncoding,
    +    ]
    +    CoreFoundation.CFStringGetCString.restype = c_bool
    +
    +    CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex]
    +    CoreFoundation.CFDataCreate.restype = CFDataRef
    +
    +    CoreFoundation.CFDataGetLength.argtypes = [CFDataRef]
    +    CoreFoundation.CFDataGetLength.restype = CFIndex
    +
    +    CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef]
    +    CoreFoundation.CFDataGetBytePtr.restype = c_void_p
    +
    +    CoreFoundation.CFDictionaryCreate.argtypes = [
    +        CFAllocatorRef,
    +        POINTER(CFTypeRef),
    +        POINTER(CFTypeRef),
    +        CFIndex,
    +        CFDictionaryKeyCallBacks,
    +        CFDictionaryValueCallBacks,
    +    ]
    +    CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef
    +
    +    CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef]
    +    CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef
    +
    +    CoreFoundation.CFArrayCreate.argtypes = [
    +        CFAllocatorRef,
    +        POINTER(CFTypeRef),
    +        CFIndex,
    +        CFArrayCallBacks,
    +    ]
    +    CoreFoundation.CFArrayCreate.restype = CFArrayRef
    +
    +    CoreFoundation.CFArrayCreateMutable.argtypes = [
    +        CFAllocatorRef,
    +        CFIndex,
    +        CFArrayCallBacks,
    +    ]
    +    CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef
    +
    +    CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p]
    +    CoreFoundation.CFArrayAppendValue.restype = None
    +
    +    CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef]
    +    CoreFoundation.CFArrayGetCount.restype = CFIndex
    +
    +    CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex]
    +    CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p
    +
    +    CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll(
    +        CoreFoundation, "kCFAllocatorDefault"
    +    )
    +    CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll(
    +        CoreFoundation, "kCFTypeArrayCallBacks"
    +    )
    +    CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll(
    +        CoreFoundation, "kCFTypeDictionaryKeyCallBacks"
    +    )
    +    CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll(
    +        CoreFoundation, "kCFTypeDictionaryValueCallBacks"
    +    )
    +
    +    CoreFoundation.CFTypeRef = CFTypeRef
    +    CoreFoundation.CFArrayRef = CFArrayRef
    +    CoreFoundation.CFStringRef = CFStringRef
    +    CoreFoundation.CFDictionaryRef = CFDictionaryRef
    +
    +except (AttributeError):
    +    raise ImportError("Error initializing ctypes")
    +
    +
    +class CFConst(object):
    +    """
    +    A class object that acts as essentially a namespace for CoreFoundation
    +    constants.
    +    """
    +
    +    kCFStringEncodingUTF8 = CFStringEncoding(0x08000100)
    +
    +
    +class SecurityConst(object):
    +    """
    +    A class object that acts as essentially a namespace for Security constants.
    +    """
    +
    +    kSSLSessionOptionBreakOnServerAuth = 0
    +
    +    kSSLProtocol2 = 1
    +    kSSLProtocol3 = 2
    +    kTLSProtocol1 = 4
    +    kTLSProtocol11 = 7
    +    kTLSProtocol12 = 8
    +    # SecureTransport does not support TLS 1.3 even if there's a constant for it
    +    kTLSProtocol13 = 10
    +    kTLSProtocolMaxSupported = 999
    +
    +    kSSLClientSide = 1
    +    kSSLStreamType = 0
    +
    +    kSecFormatPEMSequence = 10
    +
    +    kSecTrustResultInvalid = 0
    +    kSecTrustResultProceed = 1
    +    # This gap is present on purpose: this was kSecTrustResultConfirm, which
    +    # is deprecated.
    +    kSecTrustResultDeny = 3
    +    kSecTrustResultUnspecified = 4
    +    kSecTrustResultRecoverableTrustFailure = 5
    +    kSecTrustResultFatalTrustFailure = 6
    +    kSecTrustResultOtherError = 7
    +
    +    errSSLProtocol = -9800
    +    errSSLWouldBlock = -9803
    +    errSSLClosedGraceful = -9805
    +    errSSLClosedNoNotify = -9816
    +    errSSLClosedAbort = -9806
    +
    +    errSSLXCertChainInvalid = -9807
    +    errSSLCrypto = -9809
    +    errSSLInternal = -9810
    +    errSSLCertExpired = -9814
    +    errSSLCertNotYetValid = -9815
    +    errSSLUnknownRootCert = -9812
    +    errSSLNoRootCert = -9813
    +    errSSLHostNameMismatch = -9843
    +    errSSLPeerHandshakeFail = -9824
    +    errSSLPeerUserCancelled = -9839
    +    errSSLWeakPeerEphemeralDHKey = -9850
    +    errSSLServerAuthCompleted = -9841
    +    errSSLRecordOverflow = -9847
    +
    +    errSecVerifyFailed = -67808
    +    errSecNoTrustSettings = -25263
    +    errSecItemNotFound = -25300
    +    errSecInvalidTrustSettings = -25262
    +
    +    # Cipher suites. We only pick the ones our default cipher string allows.
    +    # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values
    +    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C
    +    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030
    +    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B
    +    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F
    +    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9
    +    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8
    +    TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F
    +    TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E
    +    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024
    +    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028
    +    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A
    +    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014
    +    TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B
    +    TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039
    +    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023
    +    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027
    +    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009
    +    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013
    +    TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067
    +    TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033
    +    TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D
    +    TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C
    +    TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D
    +    TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C
    +    TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035
    +    TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F
    +    TLS_AES_128_GCM_SHA256 = 0x1301
    +    TLS_AES_256_GCM_SHA384 = 0x1302
    +    TLS_AES_128_CCM_8_SHA256 = 0x1305
    +    TLS_AES_128_CCM_SHA256 = 0x1304
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/low_level.py b/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/low_level.py
    new file mode 100644
    index 0000000..e60168c
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/_securetransport/low_level.py
    @@ -0,0 +1,328 @@
    +"""
    +Low-level helpers for the SecureTransport bindings.
    +
    +These are Python functions that are not directly related to the high-level APIs
    +but are necessary to get them to work. They include a whole bunch of low-level
    +CoreFoundation messing about and memory management. The concerns in this module
    +are almost entirely about trying to avoid memory leaks and providing
    +appropriate and useful assistance to the higher-level code.
    +"""
    +import base64
    +import ctypes
    +import itertools
    +import re
    +import os
    +import ssl
    +import tempfile
    +
    +from .bindings import Security, CoreFoundation, CFConst
    +
    +
    +# This regular expression is used to grab PEM data out of a PEM bundle.
    +_PEM_CERTS_RE = re.compile(
    +    b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL
    +)
    +
    +
    +def _cf_data_from_bytes(bytestring):
    +    """
    +    Given a bytestring, create a CFData object from it. This CFData object must
    +    be CFReleased by the caller.
    +    """
    +    return CoreFoundation.CFDataCreate(
    +        CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring)
    +    )
    +
    +
    +def _cf_dictionary_from_tuples(tuples):
    +    """
    +    Given a list of Python tuples, create an associated CFDictionary.
    +    """
    +    dictionary_size = len(tuples)
    +
    +    # We need to get the dictionary keys and values out in the same order.
    +    keys = (t[0] for t in tuples)
    +    values = (t[1] for t in tuples)
    +    cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys)
    +    cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values)
    +
    +    return CoreFoundation.CFDictionaryCreate(
    +        CoreFoundation.kCFAllocatorDefault,
    +        cf_keys,
    +        cf_values,
    +        dictionary_size,
    +        CoreFoundation.kCFTypeDictionaryKeyCallBacks,
    +        CoreFoundation.kCFTypeDictionaryValueCallBacks,
    +    )
    +
    +
    +def _cf_string_to_unicode(value):
    +    """
    +    Creates a Unicode string from a CFString object. Used entirely for error
    +    reporting.
    +
    +    Yes, it annoys me quite a lot that this function is this complex.
    +    """
    +    value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p))
    +
    +    string = CoreFoundation.CFStringGetCStringPtr(
    +        value_as_void_p, CFConst.kCFStringEncodingUTF8
    +    )
    +    if string is None:
    +        buffer = ctypes.create_string_buffer(1024)
    +        result = CoreFoundation.CFStringGetCString(
    +            value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8
    +        )
    +        if not result:
    +            raise OSError("Error copying C string from CFStringRef")
    +        string = buffer.value
    +    if string is not None:
    +        string = string.decode("utf-8")
    +    return string
    +
    +
    +def _assert_no_error(error, exception_class=None):
    +    """
    +    Checks the return code and throws an exception if there is an error to
    +    report
    +    """
    +    if error == 0:
    +        return
    +
    +    cf_error_string = Security.SecCopyErrorMessageString(error, None)
    +    output = _cf_string_to_unicode(cf_error_string)
    +    CoreFoundation.CFRelease(cf_error_string)
    +
    +    if output is None or output == u"":
    +        output = u"OSStatus %s" % error
    +
    +    if exception_class is None:
    +        exception_class = ssl.SSLError
    +
    +    raise exception_class(output)
    +
    +
    +def _cert_array_from_pem(pem_bundle):
    +    """
    +    Given a bundle of certs in PEM format, turns them into a CFArray of certs
    +    that can be used to validate a cert chain.
    +    """
    +    # Normalize the PEM bundle's line endings.
    +    pem_bundle = pem_bundle.replace(b"\r\n", b"\n")
    +
    +    der_certs = [
    +        base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle)
    +    ]
    +    if not der_certs:
    +        raise ssl.SSLError("No root certificates specified")
    +
    +    cert_array = CoreFoundation.CFArrayCreateMutable(
    +        CoreFoundation.kCFAllocatorDefault,
    +        0,
    +        ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks),
    +    )
    +    if not cert_array:
    +        raise ssl.SSLError("Unable to allocate memory!")
    +
    +    try:
    +        for der_bytes in der_certs:
    +            certdata = _cf_data_from_bytes(der_bytes)
    +            if not certdata:
    +                raise ssl.SSLError("Unable to allocate memory!")
    +            cert = Security.SecCertificateCreateWithData(
    +                CoreFoundation.kCFAllocatorDefault, certdata
    +            )
    +            CoreFoundation.CFRelease(certdata)
    +            if not cert:
    +                raise ssl.SSLError("Unable to build cert object!")
    +
    +            CoreFoundation.CFArrayAppendValue(cert_array, cert)
    +            CoreFoundation.CFRelease(cert)
    +    except Exception:
    +        # We need to free the array before the exception bubbles further.
    +        # We only want to do that if an error occurs: otherwise, the caller
    +        # should free.
    +        CoreFoundation.CFRelease(cert_array)
    +
    +    return cert_array
    +
    +
    +def _is_cert(item):
    +    """
    +    Returns True if a given CFTypeRef is a certificate.
    +    """
    +    expected = Security.SecCertificateGetTypeID()
    +    return CoreFoundation.CFGetTypeID(item) == expected
    +
    +
    +def _is_identity(item):
    +    """
    +    Returns True if a given CFTypeRef is an identity.
    +    """
    +    expected = Security.SecIdentityGetTypeID()
    +    return CoreFoundation.CFGetTypeID(item) == expected
    +
    +
    +def _temporary_keychain():
    +    """
    +    This function creates a temporary Mac keychain that we can use to work with
    +    credentials. This keychain uses a one-time password and a temporary file to
    +    store the data. We expect to have one keychain per socket. The returned
    +    SecKeychainRef must be freed by the caller, including calling
    +    SecKeychainDelete.
    +
    +    Returns a tuple of the SecKeychainRef and the path to the temporary
    +    directory that contains it.
    +    """
    +    # Unfortunately, SecKeychainCreate requires a path to a keychain. This
    +    # means we cannot use mkstemp to use a generic temporary file. Instead,
    +    # we're going to create a temporary directory and a filename to use there.
    +    # This filename will be 8 random bytes expanded into base64. We also need
    +    # some random bytes to password-protect the keychain we're creating, so we
    +    # ask for 40 random bytes.
    +    random_bytes = os.urandom(40)
    +    filename = base64.b16encode(random_bytes[:8]).decode("utf-8")
    +    password = base64.b16encode(random_bytes[8:])  # Must be valid UTF-8
    +    tempdirectory = tempfile.mkdtemp()
    +
    +    keychain_path = os.path.join(tempdirectory, filename).encode("utf-8")
    +
    +    # We now want to create the keychain itself.
    +    keychain = Security.SecKeychainRef()
    +    status = Security.SecKeychainCreate(
    +        keychain_path, len(password), password, False, None, ctypes.byref(keychain)
    +    )
    +    _assert_no_error(status)
    +
    +    # Having created the keychain, we want to pass it off to the caller.
    +    return keychain, tempdirectory
    +
    +
    +def _load_items_from_file(keychain, path):
    +    """
    +    Given a single file, loads all the trust objects from it into arrays and
    +    the keychain.
    +    Returns a tuple of lists: the first list is a list of identities, the
    +    second a list of certs.
    +    """
    +    certificates = []
    +    identities = []
    +    result_array = None
    +
    +    with open(path, "rb") as f:
    +        raw_filedata = f.read()
    +
    +    try:
    +        filedata = CoreFoundation.CFDataCreate(
    +            CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata)
    +        )
    +        result_array = CoreFoundation.CFArrayRef()
    +        result = Security.SecItemImport(
    +            filedata,  # cert data
    +            None,  # Filename, leaving it out for now
    +            None,  # What the type of the file is, we don't care
    +            None,  # what's in the file, we don't care
    +            0,  # import flags
    +            None,  # key params, can include passphrase in the future
    +            keychain,  # The keychain to insert into
    +            ctypes.byref(result_array),  # Results
    +        )
    +        _assert_no_error(result)
    +
    +        # A CFArray is not very useful to us as an intermediary
    +        # representation, so we are going to extract the objects we want
    +        # and then free the array. We don't need to keep hold of keys: the
    +        # keychain already has them!
    +        result_count = CoreFoundation.CFArrayGetCount(result_array)
    +        for index in range(result_count):
    +            item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index)
    +            item = ctypes.cast(item, CoreFoundation.CFTypeRef)
    +
    +            if _is_cert(item):
    +                CoreFoundation.CFRetain(item)
    +                certificates.append(item)
    +            elif _is_identity(item):
    +                CoreFoundation.CFRetain(item)
    +                identities.append(item)
    +    finally:
    +        if result_array:
    +            CoreFoundation.CFRelease(result_array)
    +
    +        CoreFoundation.CFRelease(filedata)
    +
    +    return (identities, certificates)
    +
    +
    +def _load_client_cert_chain(keychain, *paths):
    +    """
    +    Load certificates and maybe keys from a number of files. Has the end goal
    +    of returning a CFArray containing one SecIdentityRef, and then zero or more
    +    SecCertificateRef objects, suitable for use as a client certificate trust
    +    chain.
    +    """
    +    # Ok, the strategy.
    +    #
    +    # This relies on knowing that macOS will not give you a SecIdentityRef
    +    # unless you have imported a key into a keychain. This is a somewhat
    +    # artificial limitation of macOS (for example, it doesn't necessarily
    +    # affect iOS), but there is nothing inside Security.framework that lets you
    +    # get a SecIdentityRef without having a key in a keychain.
    +    #
    +    # So the policy here is we take all the files and iterate them in order.
    +    # Each one will use SecItemImport to have one or more objects loaded from
    +    # it. We will also point at a keychain that macOS can use to work with the
    +    # private key.
    +    #
    +    # Once we have all the objects, we'll check what we actually have. If we
    +    # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise,
    +    # we'll take the first certificate (which we assume to be our leaf) and
    +    # ask the keychain to give us a SecIdentityRef with that cert's associated
    +    # key.
    +    #
    +    # We'll then return a CFArray containing the trust chain: one
    +    # SecIdentityRef and then zero-or-more SecCertificateRef objects. The
    +    # responsibility for freeing this CFArray will be with the caller. This
    +    # CFArray must remain alive for the entire connection, so in practice it
    +    # will be stored with a single SSLSocket, along with the reference to the
    +    # keychain.
    +    certificates = []
    +    identities = []
    +
    +    # Filter out bad paths.
    +    paths = (path for path in paths if path)
    +
    +    try:
    +        for file_path in paths:
    +            new_identities, new_certs = _load_items_from_file(keychain, file_path)
    +            identities.extend(new_identities)
    +            certificates.extend(new_certs)
    +
    +        # Ok, we have everything. The question is: do we have an identity? If
    +        # not, we want to grab one from the first cert we have.
    +        if not identities:
    +            new_identity = Security.SecIdentityRef()
    +            status = Security.SecIdentityCreateWithCertificate(
    +                keychain, certificates[0], ctypes.byref(new_identity)
    +            )
    +            _assert_no_error(status)
    +            identities.append(new_identity)
    +
    +            # We now want to release the original certificate, as we no longer
    +            # need it.
    +            CoreFoundation.CFRelease(certificates.pop(0))
    +
    +        # We now need to build a new CFArray that holds the trust chain.
    +        trust_chain = CoreFoundation.CFArrayCreateMutable(
    +            CoreFoundation.kCFAllocatorDefault,
    +            0,
    +            ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks),
    +        )
    +        for item in itertools.chain(identities, certificates):
    +            # ArrayAppendValue does a CFRetain on the item. That's fine,
    +            # because the finally block will release our other refs to them.
    +            CoreFoundation.CFArrayAppendValue(trust_chain, item)
    +
    +        return trust_chain
    +    finally:
    +        for obj in itertools.chain(identities, certificates):
    +            CoreFoundation.CFRelease(obj)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/appengine.py b/venv/lib/python3.7/site-packages/urllib3/contrib/appengine.py
    new file mode 100644
    index 0000000..9b7044f
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/appengine.py
    @@ -0,0 +1,314 @@
    +"""
    +This module provides a pool manager that uses Google App Engine's
    +`URLFetch Service `_.
    +
    +Example usage::
    +
    +    from urllib3 import PoolManager
    +    from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox
    +
    +    if is_appengine_sandbox():
    +        # AppEngineManager uses AppEngine's URLFetch API behind the scenes
    +        http = AppEngineManager()
    +    else:
    +        # PoolManager uses a socket-level API behind the scenes
    +        http = PoolManager()
    +
    +    r = http.request('GET', 'https://google.com/')
    +
    +There are `limitations `_ to the URLFetch service and it may not be
    +the best choice for your application. There are three options for using
    +urllib3 on Google App Engine:
    +
    +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is
    +   cost-effective in many circumstances as long as your usage is within the
    +   limitations.
    +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets.
    +   Sockets also have `limitations and restrictions
    +   `_ and have a lower free quota than URLFetch.
    +   To use sockets, be sure to specify the following in your ``app.yaml``::
    +
    +        env_variables:
    +            GAE_USE_SOCKETS_HTTPLIB : 'true'
    +
    +3. If you are using `App Engine Flexible
    +`_, you can use the standard
    +:class:`PoolManager` without any configuration or special environment variables.
    +"""
    +
    +from __future__ import absolute_import
    +import io
    +import logging
    +import warnings
    +from ..packages.six.moves.urllib.parse import urljoin
    +
    +from ..exceptions import (
    +    HTTPError,
    +    HTTPWarning,
    +    MaxRetryError,
    +    ProtocolError,
    +    TimeoutError,
    +    SSLError,
    +)
    +
    +from ..request import RequestMethods
    +from ..response import HTTPResponse
    +from ..util.timeout import Timeout
    +from ..util.retry import Retry
    +from . import _appengine_environ
    +
    +try:
    +    from google.appengine.api import urlfetch
    +except ImportError:
    +    urlfetch = None
    +
    +
    +log = logging.getLogger(__name__)
    +
    +
    +class AppEnginePlatformWarning(HTTPWarning):
    +    pass
    +
    +
    +class AppEnginePlatformError(HTTPError):
    +    pass
    +
    +
    +class AppEngineManager(RequestMethods):
    +    """
    +    Connection manager for Google App Engine sandbox applications.
    +
    +    This manager uses the URLFetch service directly instead of using the
    +    emulated httplib, and is subject to URLFetch limitations as described in
    +    the App Engine documentation `here
    +    `_.
    +
    +    Notably it will raise an :class:`AppEnginePlatformError` if:
    +        * URLFetch is not available.
    +        * If you attempt to use this on App Engine Flexible, as full socket
    +          support is available.
    +        * If a request size is more than 10 megabytes.
    +        * If a response size is more than 32 megabtyes.
    +        * If you use an unsupported request method such as OPTIONS.
    +
    +    Beyond those cases, it will raise normal urllib3 errors.
    +    """
    +
    +    def __init__(
    +        self,
    +        headers=None,
    +        retries=None,
    +        validate_certificate=True,
    +        urlfetch_retries=True,
    +    ):
    +        if not urlfetch:
    +            raise AppEnginePlatformError(
    +                "URLFetch is not available in this environment."
    +            )
    +
    +        warnings.warn(
    +            "urllib3 is using URLFetch on Google App Engine sandbox instead "
    +            "of sockets. To use sockets directly instead of URLFetch see "
    +            "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.",
    +            AppEnginePlatformWarning,
    +        )
    +
    +        RequestMethods.__init__(self, headers)
    +        self.validate_certificate = validate_certificate
    +        self.urlfetch_retries = urlfetch_retries
    +
    +        self.retries = retries or Retry.DEFAULT
    +
    +    def __enter__(self):
    +        return self
    +
    +    def __exit__(self, exc_type, exc_val, exc_tb):
    +        # Return False to re-raise any potential exceptions
    +        return False
    +
    +    def urlopen(
    +        self,
    +        method,
    +        url,
    +        body=None,
    +        headers=None,
    +        retries=None,
    +        redirect=True,
    +        timeout=Timeout.DEFAULT_TIMEOUT,
    +        **response_kw
    +    ):
    +
    +        retries = self._get_retries(retries, redirect)
    +
    +        try:
    +            follow_redirects = redirect and retries.redirect != 0 and retries.total
    +            response = urlfetch.fetch(
    +                url,
    +                payload=body,
    +                method=method,
    +                headers=headers or {},
    +                allow_truncated=False,
    +                follow_redirects=self.urlfetch_retries and follow_redirects,
    +                deadline=self._get_absolute_timeout(timeout),
    +                validate_certificate=self.validate_certificate,
    +            )
    +        except urlfetch.DeadlineExceededError as e:
    +            raise TimeoutError(self, e)
    +
    +        except urlfetch.InvalidURLError as e:
    +            if "too large" in str(e):
    +                raise AppEnginePlatformError(
    +                    "URLFetch request too large, URLFetch only "
    +                    "supports requests up to 10mb in size.",
    +                    e,
    +                )
    +            raise ProtocolError(e)
    +
    +        except urlfetch.DownloadError as e:
    +            if "Too many redirects" in str(e):
    +                raise MaxRetryError(self, url, reason=e)
    +            raise ProtocolError(e)
    +
    +        except urlfetch.ResponseTooLargeError as e:
    +            raise AppEnginePlatformError(
    +                "URLFetch response too large, URLFetch only supports"
    +                "responses up to 32mb in size.",
    +                e,
    +            )
    +
    +        except urlfetch.SSLCertificateError as e:
    +            raise SSLError(e)
    +
    +        except urlfetch.InvalidMethodError as e:
    +            raise AppEnginePlatformError(
    +                "URLFetch does not support method: %s" % method, e
    +            )
    +
    +        http_response = self._urlfetch_response_to_http_response(
    +            response, retries=retries, **response_kw
    +        )
    +
    +        # Handle redirect?
    +        redirect_location = redirect and http_response.get_redirect_location()
    +        if redirect_location:
    +            # Check for redirect response
    +            if self.urlfetch_retries and retries.raise_on_redirect:
    +                raise MaxRetryError(self, url, "too many redirects")
    +            else:
    +                if http_response.status == 303:
    +                    method = "GET"
    +
    +                try:
    +                    retries = retries.increment(
    +                        method, url, response=http_response, _pool=self
    +                    )
    +                except MaxRetryError:
    +                    if retries.raise_on_redirect:
    +                        raise MaxRetryError(self, url, "too many redirects")
    +                    return http_response
    +
    +                retries.sleep_for_retry(http_response)
    +                log.debug("Redirecting %s -> %s", url, redirect_location)
    +                redirect_url = urljoin(url, redirect_location)
    +                return self.urlopen(
    +                    method,
    +                    redirect_url,
    +                    body,
    +                    headers,
    +                    retries=retries,
    +                    redirect=redirect,
    +                    timeout=timeout,
    +                    **response_kw
    +                )
    +
    +        # Check if we should retry the HTTP response.
    +        has_retry_after = bool(http_response.getheader("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)
    +            retries.sleep(http_response)
    +            return self.urlopen(
    +                method,
    +                url,
    +                body=body,
    +                headers=headers,
    +                retries=retries,
    +                redirect=redirect,
    +                timeout=timeout,
    +                **response_kw
    +            )
    +
    +        return http_response
    +
    +    def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):
    +
    +        if is_prod_appengine():
    +            # Production GAE handles deflate encoding automatically, but does
    +            # not remove the encoding header.
    +            content_encoding = urlfetch_resp.headers.get("content-encoding")
    +
    +            if content_encoding == "deflate":
    +                del urlfetch_resp.headers["content-encoding"]
    +
    +        transfer_encoding = urlfetch_resp.headers.get("transfer-encoding")
    +        # We have a full response's content,
    +        # so let's make sure we don't report ourselves as chunked data.
    +        if transfer_encoding == "chunked":
    +            encodings = transfer_encoding.split(",")
    +            encodings.remove("chunked")
    +            urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings)
    +
    +        original_response = HTTPResponse(
    +            # In order for decoding to work, we must present the content as
    +            # a file-like object.
    +            body=io.BytesIO(urlfetch_resp.content),
    +            msg=urlfetch_resp.header_msg,
    +            headers=urlfetch_resp.headers,
    +            status=urlfetch_resp.status_code,
    +            **response_kw
    +        )
    +
    +        return HTTPResponse(
    +            body=io.BytesIO(urlfetch_resp.content),
    +            headers=urlfetch_resp.headers,
    +            status=urlfetch_resp.status_code,
    +            original_response=original_response,
    +            **response_kw
    +        )
    +
    +    def _get_absolute_timeout(self, timeout):
    +        if timeout is Timeout.DEFAULT_TIMEOUT:
    +            return None  # Defer to URLFetch's default.
    +        if isinstance(timeout, Timeout):
    +            if timeout._read is not None or timeout._connect is not None:
    +                warnings.warn(
    +                    "URLFetch does not support granular timeout settings, "
    +                    "reverting to total or default URLFetch timeout.",
    +                    AppEnginePlatformWarning,
    +                )
    +            return timeout.total
    +        return timeout
    +
    +    def _get_retries(self, retries, redirect):
    +        if not isinstance(retries, Retry):
    +            retries = Retry.from_int(retries, redirect=redirect, default=self.retries)
    +
    +        if retries.connect or retries.read or retries.redirect:
    +            warnings.warn(
    +                "URLFetch only supports total retries and does not "
    +                "recognize connect, read, or redirect retry parameters.",
    +                AppEnginePlatformWarning,
    +            )
    +
    +        return retries
    +
    +
    +# Alias methods from _appengine_environ to maintain public API interface.
    +
    +is_appengine = _appengine_environ.is_appengine
    +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox
    +is_local_appengine = _appengine_environ.is_local_appengine
    +is_prod_appengine = _appengine_environ.is_prod_appengine
    +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/ntlmpool.py b/venv/lib/python3.7/site-packages/urllib3/contrib/ntlmpool.py
    new file mode 100644
    index 0000000..1fd242a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/ntlmpool.py
    @@ -0,0 +1,121 @@
    +"""
    +NTLM authenticating pool, contributed by erikcederstran
    +
    +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
    +"""
    +from __future__ import absolute_import
    +
    +from logging import getLogger
    +from ntlm import ntlm
    +
    +from .. import HTTPSConnectionPool
    +from ..packages.six.moves.http_client import HTTPSConnection
    +
    +
    +log = getLogger(__name__)
    +
    +
    +class NTLMConnectionPool(HTTPSConnectionPool):
    +    """
    +    Implements an NTLM authentication version of an urllib3 connection pool
    +    """
    +
    +    scheme = "https"
    +
    +    def __init__(self, user, pw, authurl, *args, **kwargs):
    +        """
    +        authurl is a random URL on the server that is protected by NTLM.
    +        user is the Windows user, probably in the DOMAIN\\username format.
    +        pw is the password for the user.
    +        """
    +        super(NTLMConnectionPool, self).__init__(*args, **kwargs)
    +        self.authurl = authurl
    +        self.rawuser = user
    +        user_parts = user.split("\\", 1)
    +        self.domain = user_parts[0].upper()
    +        self.user = user_parts[1]
    +        self.pw = pw
    +
    +    def _new_conn(self):
    +        # Performs the NTLM handshake that secures the connection. The socket
    +        # must be kept open while requests are performed.
    +        self.num_connections += 1
    +        log.debug(
    +            "Starting NTLM HTTPS connection no. %d: https://%s%s",
    +            self.num_connections,
    +            self.host,
    +            self.authurl,
    +        )
    +
    +        headers = {"Connection": "Keep-Alive"}
    +        req_header = "Authorization"
    +        resp_header = "www-authenticate"
    +
    +        conn = HTTPSConnection(host=self.host, port=self.port)
    +
    +        # Send negotiation message
    +        headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE(
    +            self.rawuser
    +        )
    +        log.debug("Request headers: %s", headers)
    +        conn.request("GET", self.authurl, None, headers)
    +        res = conn.getresponse()
    +        reshdr = dict(res.getheaders())
    +        log.debug("Response status: %s %s", res.status, res.reason)
    +        log.debug("Response headers: %s", reshdr)
    +        log.debug("Response data: %s [...]", res.read(100))
    +
    +        # Remove the reference to the socket, so that it can not be closed by
    +        # the response object (we want to keep the socket open)
    +        res.fp = None
    +
    +        # Server should respond with a challenge message
    +        auth_header_values = reshdr[resp_header].split(", ")
    +        auth_header_value = None
    +        for s in auth_header_values:
    +            if s[:5] == "NTLM ":
    +                auth_header_value = s[5:]
    +        if auth_header_value is None:
    +            raise Exception(
    +                "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header])
    +            )
    +
    +        # Send authentication message
    +        ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE(
    +            auth_header_value
    +        )
    +        auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(
    +            ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags
    +        )
    +        headers[req_header] = "NTLM %s" % auth_msg
    +        log.debug("Request headers: %s", headers)
    +        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 data: %s [...]", res.read()[:100])
    +        if res.status != 200:
    +            if res.status == 401:
    +                raise Exception("Server rejected request: wrong username or password")
    +            raise Exception("Wrong server response: %s %s" % (res.status, res.reason))
    +
    +        res.fp = None
    +        log.debug("Connection established")
    +        return conn
    +
    +    def urlopen(
    +        self,
    +        method,
    +        url,
    +        body=None,
    +        headers=None,
    +        retries=3,
    +        redirect=True,
    +        assert_same_host=True,
    +    ):
    +        if headers is None:
    +            headers = {}
    +        headers["Connection"] = "Keep-Alive"
    +        return super(NTLMConnectionPool, self).urlopen(
    +            method, url, body, headers, retries, redirect, assert_same_host
    +        )
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py b/venv/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py
    new file mode 100644
    index 0000000..3051ef3
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py
    @@ -0,0 +1,498 @@
    +"""
    +SSL with SNI_-support for Python 2. Follow these instructions if you would
    +like to verify SSL certificates in Python 2. Note, the default libraries do
    +*not* do certificate checking; you need to do additional work to validate
    +certificates yourself.
    +
    +This needs the following packages installed:
    +
    +* pyOpenSSL (tested with 16.0.0)
    +* cryptography (minimum 1.3.4, from pyopenssl)
    +* idna (minimum 2.0, from cryptography)
    +
    +However, pyopenssl depends on cryptography, which depends on idna, so while we
    +use all three directly here we end up having relatively few packages required.
    +
    +You can install them with the following command:
    +
    +    pip install pyopenssl cryptography idna
    +
    +To activate certificate checking, call
    +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code
    +before you begin making HTTP requests. This can be done in a ``sitecustomize``
    +module, or at any other time before your application begins using ``urllib3``,
    +like this::
    +
    +    try:
    +        import urllib3.contrib.pyopenssl
    +        urllib3.contrib.pyopenssl.inject_into_urllib3()
    +    except ImportError:
    +        pass
    +
    +Now you can use :mod:`urllib3` as you normally would, and it will support SNI
    +when the required modules are installed.
    +
    +Activating this module also has the positive side effect of disabling SSL/TLS
    +compression in Python 2 (see `CRIME attack`_).
    +
    +If you want to configure the default list of supported cipher suites, you can
    +set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
    +
    +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
    +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
    +"""
    +from __future__ import absolute_import
    +
    +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
    +except ImportError:
    +    # UnsupportedExtension is gone in cryptography >= 2.1.0
    +    class UnsupportedExtension(Exception):
    +        pass
    +
    +
    +from socket import timeout, error as SocketError
    +from io import BytesIO
    +
    +try:  # Platform-specific: Python 2
    +    from socket import _fileobject
    +except ImportError:  # Platform-specific: Python 3
    +    _fileobject = None
    +    from ..packages.backports.makefile import backport_makefile
    +
    +import logging
    +import ssl
    +from ..packages import six
    +import sys
    +
    +from .. import util
    +
    +
    +__all__ = ["inject_into_urllib3", "extract_from_urllib3"]
    +
    +# SNI always works.
    +HAS_SNI = True
    +
    +# Map from urllib3 to PyOpenSSL compatible parameter-values.
    +_openssl_versions = {
    +    util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD,
    +    ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
    +}
    +
    +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"):
    +    _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD
    +
    +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"):
    +    _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
    +
    +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"):
    +    _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
    +
    +
    +_stdlib_to_openssl_verify = {
    +    ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
    +    ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
    +    ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
    +    + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
    +}
    +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items())
    +
    +# OpenSSL will only write 16K at a time
    +SSL_WRITE_BLOCKSIZE = 16384
    +
    +orig_util_HAS_SNI = util.HAS_SNI
    +orig_util_SSLContext = util.ssl_.SSLContext
    +
    +
    +log = logging.getLogger(__name__)
    +
    +
    +def inject_into_urllib3():
    +    "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support."
    +
    +    _validate_dependencies_met()
    +
    +    util.SSLContext = PyOpenSSLContext
    +    util.ssl_.SSLContext = PyOpenSSLContext
    +    util.HAS_SNI = HAS_SNI
    +    util.ssl_.HAS_SNI = HAS_SNI
    +    util.IS_PYOPENSSL = True
    +    util.ssl_.IS_PYOPENSSL = True
    +
    +
    +def extract_from_urllib3():
    +    "Undo monkey-patching by :func:`inject_into_urllib3`."
    +
    +    util.SSLContext = orig_util_SSLContext
    +    util.ssl_.SSLContext = orig_util_SSLContext
    +    util.HAS_SNI = orig_util_HAS_SNI
    +    util.ssl_.HAS_SNI = orig_util_HAS_SNI
    +    util.IS_PYOPENSSL = False
    +    util.ssl_.IS_PYOPENSSL = False
    +
    +
    +def _validate_dependencies_met():
    +    """
    +    Verifies that PyOpenSSL's package-level dependencies have been met.
    +    Throws `ImportError` if they are not met.
    +    """
    +    # Method added in `cryptography==1.1`; not available in older versions
    +    from cryptography.x509.extensions import Extensions
    +
    +    if getattr(Extensions, "get_extension_for_class", None) is None:
    +        raise ImportError(
    +            "'cryptography' module missing required functionality.  "
    +            "Try upgrading to v1.3.4 or newer."
    +        )
    +
    +    # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509
    +    # attribute is only present on those versions.
    +    from OpenSSL.crypto import X509
    +
    +    x509 = X509()
    +    if getattr(x509, "_x509", None) is None:
    +        raise ImportError(
    +            "'pyOpenSSL' module missing required functionality. "
    +            "Try upgrading to v0.14 or newer."
    +        )
    +
    +
    +def _dnsname_to_stdlib(name):
    +    """
    +    Converts a dNSName SubjectAlternativeName field to the form used by the
    +    standard library on the given Python version.
    +
    +    Cryptography produces a dNSName as a unicode string that was idna-decoded
    +    from ASCII bytes. We need to idna-encode that string to get it back, and
    +    then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib
    +    uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8).
    +
    +    If the name cannot be idna-encoded then we return None signalling that
    +    the name given should be skipped.
    +    """
    +
    +    def idna_encode(name):
    +        """
    +        Borrowed wholesale from the Python Cryptography Project. It turns out
    +        that we can't just safely call `idna.encode`: it can explode for
    +        wildcard names. This avoids that problem.
    +        """
    +        import idna
    +
    +        try:
    +            for prefix in [u"*.", u"."]:
    +                if name.startswith(prefix):
    +                    name = name[len(prefix) :]
    +                    return prefix.encode("ascii") + idna.encode(name)
    +            return idna.encode(name)
    +        except idna.core.IDNAError:
    +            return None
    +
    +    # Don't send IPv6 addresses through the IDNA encoder.
    +    if ":" in name:
    +        return name
    +
    +    name = idna_encode(name)
    +    if name is None:
    +        return None
    +    elif sys.version_info >= (3, 0):
    +        name = name.decode("utf-8")
    +    return name
    +
    +
    +def get_subj_alt_name(peer_cert):
    +    """
    +    Given an PyOpenSSL certificate, provides all the subject alternative names.
    +    """
    +    # Pass the cert to cryptography, which has much better APIs for this.
    +    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)
    +
    +    # We want to find the SAN extension. Ask Cryptography to locate it (it's
    +    # faster than looping in Python)
    +    try:
    +        ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
    +    except x509.ExtensionNotFound:
    +        # No such extension, return the empty list.
    +        return []
    +    except (
    +        x509.DuplicateExtension,
    +        UnsupportedExtension,
    +        x509.UnsupportedGeneralNameType,
    +        UnicodeError,
    +    ) as e:
    +        # A problem has been found with the quality of the certificate. Assume
    +        # no SAN field is present.
    +        log.warning(
    +            "A problem was encountered with the certificate that prevented "
    +            "urllib3 from finding the SubjectAlternativeName field. This can "
    +            "affect certificate validation. The error was %s",
    +            e,
    +        )
    +        return []
    +
    +    # We want to return dNSName and iPAddress fields. We need to cast the IPs
    +    # back to strings because the match_hostname function wants them as
    +    # strings.
    +    # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8
    +    # decoded. This is pretty frustrating, but that's what the standard library
    +    # does with certificates, and so we need to attempt to do the same.
    +    # We also want to skip over names which cannot be idna encoded.
    +    names = [
    +        ("DNS", name)
    +        for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName))
    +        if name is not None
    +    ]
    +    names.extend(
    +        ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress)
    +    )
    +
    +    return names
    +
    +
    +class WrappedSocket(object):
    +    """API-compatibility wrapper for Python OpenSSL's Connection-class.
    +
    +    Note: _makefile_refs, _drop() and _reuse() are needed for the garbage
    +    collector of pypy.
    +    """
    +
    +    def __init__(self, connection, socket, suppress_ragged_eofs=True):
    +        self.connection = connection
    +        self.socket = socket
    +        self.suppress_ragged_eofs = suppress_ragged_eofs
    +        self._makefile_refs = 0
    +        self._closed = False
    +
    +    def fileno(self):
    +        return self.socket.fileno()
    +
    +    # Copy-pasted from Python 3.5 source code
    +    def _decref_socketios(self):
    +        if self._makefile_refs > 0:
    +            self._makefile_refs -= 1
    +        if self._closed:
    +            self.close()
    +
    +    def recv(self, *args, **kwargs):
    +        try:
    +            data = self.connection.recv(*args, **kwargs)
    +        except OpenSSL.SSL.SysCallError as e:
    +            if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):
    +                return b""
    +            else:
    +                raise SocketError(str(e))
    +        except OpenSSL.SSL.ZeroReturnError:
    +            if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
    +                return b""
    +            else:
    +                raise
    +        except OpenSSL.SSL.WantReadError:
    +            if not util.wait_for_read(self.socket, self.socket.gettimeout()):
    +                raise timeout("The read operation timed out")
    +            else:
    +                return self.recv(*args, **kwargs)
    +
    +        # TLS 1.3 post-handshake authentication
    +        except OpenSSL.SSL.Error as e:
    +            raise ssl.SSLError("read error: %r" % e)
    +        else:
    +            return data
    +
    +    def recv_into(self, *args, **kwargs):
    +        try:
    +            return self.connection.recv_into(*args, **kwargs)
    +        except OpenSSL.SSL.SysCallError as e:
    +            if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"):
    +                return 0
    +            else:
    +                raise SocketError(str(e))
    +        except OpenSSL.SSL.ZeroReturnError:
    +            if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
    +                return 0
    +            else:
    +                raise
    +        except OpenSSL.SSL.WantReadError:
    +            if not util.wait_for_read(self.socket, self.socket.gettimeout()):
    +                raise timeout("The read operation timed out")
    +            else:
    +                return self.recv_into(*args, **kwargs)
    +
    +        # TLS 1.3 post-handshake authentication
    +        except OpenSSL.SSL.Error as e:
    +            raise ssl.SSLError("read error: %r" % e)
    +
    +    def settimeout(self, timeout):
    +        return self.socket.settimeout(timeout)
    +
    +    def _send_until_done(self, data):
    +        while True:
    +            try:
    +                return self.connection.send(data)
    +            except OpenSSL.SSL.WantWriteError:
    +                if not util.wait_for_write(self.socket, self.socket.gettimeout()):
    +                    raise timeout()
    +                continue
    +            except OpenSSL.SSL.SysCallError as e:
    +                raise SocketError(str(e))
    +
    +    def sendall(self, data):
    +        total_sent = 0
    +        while total_sent < len(data):
    +            sent = self._send_until_done(
    +                data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]
    +            )
    +            total_sent += sent
    +
    +    def shutdown(self):
    +        # FIXME rethrow compatible exceptions should we ever use this
    +        self.connection.shutdown()
    +
    +    def close(self):
    +        if self._makefile_refs < 1:
    +            try:
    +                self._closed = True
    +                return self.connection.close()
    +            except OpenSSL.SSL.Error:
    +                return
    +        else:
    +            self._makefile_refs -= 1
    +
    +    def getpeercert(self, binary_form=False):
    +        x509 = self.connection.get_peer_certificate()
    +
    +        if not x509:
    +            return x509
    +
    +        if binary_form:
    +            return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509)
    +
    +        return {
    +            "subject": ((("commonName", x509.get_subject().CN),),),
    +            "subjectAltName": get_subj_alt_name(x509),
    +        }
    +
    +    def version(self):
    +        return self.connection.get_protocol_version_name()
    +
    +    def _reuse(self):
    +        self._makefile_refs += 1
    +
    +    def _drop(self):
    +        if self._makefile_refs < 1:
    +            self.close()
    +        else:
    +            self._makefile_refs -= 1
    +
    +
    +if _fileobject:  # Platform-specific: Python 2
    +
    +    def makefile(self, mode, bufsize=-1):
    +        self._makefile_refs += 1
    +        return _fileobject(self, mode, bufsize, close=True)
    +
    +
    +else:  # Platform-specific: Python 3
    +    makefile = backport_makefile
    +
    +WrappedSocket.makefile = makefile
    +
    +
    +class PyOpenSSLContext(object):
    +    """
    +    I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible
    +    for translating the interface of the standard library ``SSLContext`` object
    +    to calls into PyOpenSSL.
    +    """
    +
    +    def __init__(self, protocol):
    +        self.protocol = _openssl_versions[protocol]
    +        self._ctx = OpenSSL.SSL.Context(self.protocol)
    +        self._options = 0
    +        self.check_hostname = False
    +
    +    @property
    +    def options(self):
    +        return self._options
    +
    +    @options.setter
    +    def options(self, value):
    +        self._options = value
    +        self._ctx.set_options(value)
    +
    +    @property
    +    def verify_mode(self):
    +        return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()]
    +
    +    @verify_mode.setter
    +    def verify_mode(self, value):
    +        self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback)
    +
    +    def set_default_verify_paths(self):
    +        self._ctx.set_default_verify_paths()
    +
    +    def set_ciphers(self, ciphers):
    +        if isinstance(ciphers, six.text_type):
    +            ciphers = ciphers.encode("utf-8")
    +        self._ctx.set_cipher_list(ciphers)
    +
    +    def load_verify_locations(self, cafile=None, capath=None, cadata=None):
    +        if cafile is not None:
    +            cafile = cafile.encode("utf-8")
    +        if capath is not None:
    +            capath = capath.encode("utf-8")
    +        self._ctx.load_verify_locations(cafile, capath)
    +        if cadata is not None:
    +            self._ctx.load_verify_locations(BytesIO(cadata))
    +
    +    def load_cert_chain(self, certfile, keyfile=None, password=None):
    +        self._ctx.use_certificate_chain_file(certfile)
    +        if password is not None:
    +            if not isinstance(password, six.binary_type):
    +                password = password.encode("utf-8")
    +            self._ctx.set_passwd_cb(lambda *_: password)
    +        self._ctx.use_privatekey_file(keyfile or certfile)
    +
    +    def wrap_socket(
    +        self,
    +        sock,
    +        server_side=False,
    +        do_handshake_on_connect=True,
    +        suppress_ragged_eofs=True,
    +        server_hostname=None,
    +    ):
    +        cnx = OpenSSL.SSL.Connection(self._ctx, sock)
    +
    +        if isinstance(server_hostname, six.text_type):  # Platform-specific: Python 3
    +            server_hostname = server_hostname.encode("utf-8")
    +
    +        if server_hostname is not None:
    +            cnx.set_tlsext_host_name(server_hostname)
    +
    +        cnx.set_connect_state()
    +
    +        while True:
    +            try:
    +                cnx.do_handshake()
    +            except OpenSSL.SSL.WantReadError:
    +                if not util.wait_for_read(sock, sock.gettimeout()):
    +                    raise timeout("select timed out")
    +                continue
    +            except OpenSSL.SSL.Error as e:
    +                raise ssl.SSLError("bad handshake: %r" % e)
    +            break
    +
    +        return WrappedSocket(cnx, sock)
    +
    +
    +def _verify_callback(cnx, x509, err_no, err_depth, return_code):
    +    return err_no == 0
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/securetransport.py b/venv/lib/python3.7/site-packages/urllib3/contrib/securetransport.py
    new file mode 100644
    index 0000000..87d844a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/securetransport.py
    @@ -0,0 +1,859 @@
    +"""
    +SecureTranport support for urllib3 via ctypes.
    +
    +This makes platform-native TLS available to urllib3 users on macOS without the
    +use of a compiler. This is an important feature because the Python Package
    +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL
    +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve
    +this is to give macOS users an alternative solution to the problem, and that
    +solution is to use SecureTransport.
    +
    +We use ctypes here because this solution must not require a compiler. That's
    +because pip is not allowed to require a compiler either.
    +
    +This is not intended to be a seriously long-term solution to this problem.
    +The hope is that PEP 543 will eventually solve this issue for us, at which
    +point we can retire this contrib module. But in the short term, we need to
    +solve the impending tire fire that is Python on Mac without this kind of
    +contrib module. So...here we are.
    +
    +To use this module, simply import and inject it::
    +
    +    import urllib3.contrib.securetransport
    +    urllib3.contrib.securetransport.inject_into_urllib3()
    +
    +Happy TLSing!
    +
    +This code is a bastardised version of the code found in Will Bond's oscrypto
    +library. An enormous debt is owed to him for blazing this trail for us. For
    +that reason, this code should be considered to be covered both by urllib3's
    +license and by oscrypto's:
    +
    +    Copyright (c) 2015-2016 Will Bond 
    +
    +    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 absolute_import
    +
    +import contextlib
    +import ctypes
    +import errno
    +import os.path
    +import shutil
    +import socket
    +import ssl
    +import threading
    +import weakref
    +
    +from .. import util
    +from ._securetransport.bindings import Security, SecurityConst, CoreFoundation
    +from ._securetransport.low_level import (
    +    _assert_no_error,
    +    _cert_array_from_pem,
    +    _temporary_keychain,
    +    _load_client_cert_chain,
    +)
    +
    +try:  # Platform-specific: Python 2
    +    from socket import _fileobject
    +except ImportError:  # Platform-specific: Python 3
    +    _fileobject = None
    +    from ..packages.backports.makefile import backport_makefile
    +
    +__all__ = ["inject_into_urllib3", "extract_from_urllib3"]
    +
    +# SNI always works
    +HAS_SNI = True
    +
    +orig_util_HAS_SNI = util.HAS_SNI
    +orig_util_SSLContext = util.ssl_.SSLContext
    +
    +# This dictionary is used by the read callback to obtain a handle to the
    +# calling wrapped socket. This is a pretty silly approach, but for now it'll
    +# do. I feel like I should be able to smuggle a handle to the wrapped socket
    +# directly in the SSLConnectionRef, but for now this approach will work I
    +# guess.
    +#
    +# We need to lock around this structure for inserts, but we don't do it for
    +# reads/writes in the callbacks. The reasoning here goes as follows:
    +#
    +#    1. It is not possible to call into the callbacks before the dictionary is
    +#       populated, so once in the callback the id must be in the dictionary.
    +#    2. The callbacks don't mutate the dictionary, they only read from it, and
    +#       so cannot conflict with any of the insertions.
    +#
    +# This is good: if we had to lock in the callbacks we'd drastically slow down
    +# the performance of this code.
    +_connection_refs = weakref.WeakValueDictionary()
    +_connection_ref_lock = threading.Lock()
    +
    +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over
    +# for no better reason than we need *a* limit, and this one is right there.
    +SSL_WRITE_BLOCKSIZE = 16384
    +
    +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to
    +# individual cipher suites. We need to do this because this is how
    +# SecureTransport wants them.
    +CIPHER_SUITES = [
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
    +    SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
    +    SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
    +    SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
    +    SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
    +    SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
    +    SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
    +    SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
    +    SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
    +    SecurityConst.TLS_AES_256_GCM_SHA384,
    +    SecurityConst.TLS_AES_128_GCM_SHA256,
    +    SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384,
    +    SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256,
    +    SecurityConst.TLS_AES_128_CCM_8_SHA256,
    +    SecurityConst.TLS_AES_128_CCM_SHA256,
    +    SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256,
    +    SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256,
    +    SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA,
    +    SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA,
    +]
    +
    +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of
    +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version.
    +# TLSv1 to 1.2 are supported on macOS 10.8+
    +_protocol_to_min_max = {
    +    util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12)
    +}
    +
    +if hasattr(ssl, "PROTOCOL_SSLv2"):
    +    _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = (
    +        SecurityConst.kSSLProtocol2,
    +        SecurityConst.kSSLProtocol2,
    +    )
    +if hasattr(ssl, "PROTOCOL_SSLv3"):
    +    _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = (
    +        SecurityConst.kSSLProtocol3,
    +        SecurityConst.kSSLProtocol3,
    +    )
    +if hasattr(ssl, "PROTOCOL_TLSv1"):
    +    _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = (
    +        SecurityConst.kTLSProtocol1,
    +        SecurityConst.kTLSProtocol1,
    +    )
    +if hasattr(ssl, "PROTOCOL_TLSv1_1"):
    +    _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = (
    +        SecurityConst.kTLSProtocol11,
    +        SecurityConst.kTLSProtocol11,
    +    )
    +if hasattr(ssl, "PROTOCOL_TLSv1_2"):
    +    _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = (
    +        SecurityConst.kTLSProtocol12,
    +        SecurityConst.kTLSProtocol12,
    +    )
    +
    +
    +def inject_into_urllib3():
    +    """
    +    Monkey-patch urllib3 with SecureTransport-backed SSL-support.
    +    """
    +    util.SSLContext = SecureTransportContext
    +    util.ssl_.SSLContext = SecureTransportContext
    +    util.HAS_SNI = HAS_SNI
    +    util.ssl_.HAS_SNI = HAS_SNI
    +    util.IS_SECURETRANSPORT = True
    +    util.ssl_.IS_SECURETRANSPORT = True
    +
    +
    +def extract_from_urllib3():
    +    """
    +    Undo monkey-patching by :func:`inject_into_urllib3`.
    +    """
    +    util.SSLContext = orig_util_SSLContext
    +    util.ssl_.SSLContext = orig_util_SSLContext
    +    util.HAS_SNI = orig_util_HAS_SNI
    +    util.ssl_.HAS_SNI = orig_util_HAS_SNI
    +    util.IS_SECURETRANSPORT = False
    +    util.ssl_.IS_SECURETRANSPORT = False
    +
    +
    +def _read_callback(connection_id, data_buffer, data_length_pointer):
    +    """
    +    SecureTransport read callback. This is called by ST to request that data
    +    be returned from the socket.
    +    """
    +    wrapped_socket = None
    +    try:
    +        wrapped_socket = _connection_refs.get(connection_id)
    +        if wrapped_socket is None:
    +            return SecurityConst.errSSLInternal
    +        base_socket = wrapped_socket.socket
    +
    +        requested_length = data_length_pointer[0]
    +
    +        timeout = wrapped_socket.gettimeout()
    +        error = None
    +        read_count = 0
    +
    +        try:
    +            while read_count < requested_length:
    +                if timeout is None or timeout >= 0:
    +                    if not util.wait_for_read(base_socket, timeout):
    +                        raise socket.error(errno.EAGAIN, "timed out")
    +
    +                remaining = requested_length - read_count
    +                buffer = (ctypes.c_char * remaining).from_address(
    +                    data_buffer + read_count
    +                )
    +                chunk_size = base_socket.recv_into(buffer, remaining)
    +                read_count += chunk_size
    +                if not chunk_size:
    +                    if not read_count:
    +                        return SecurityConst.errSSLClosedGraceful
    +                    break
    +        except (socket.error) as e:
    +            error = e.errno
    +
    +            if error is not None and error != errno.EAGAIN:
    +                data_length_pointer[0] = read_count
    +                if error == errno.ECONNRESET or error == errno.EPIPE:
    +                    return SecurityConst.errSSLClosedAbort
    +                raise
    +
    +        data_length_pointer[0] = read_count
    +
    +        if read_count != requested_length:
    +            return SecurityConst.errSSLWouldBlock
    +
    +        return 0
    +    except Exception as e:
    +        if wrapped_socket is not None:
    +            wrapped_socket._exception = e
    +        return SecurityConst.errSSLInternal
    +
    +
    +def _write_callback(connection_id, data_buffer, data_length_pointer):
    +    """
    +    SecureTransport write callback. This is called by ST to request that data
    +    actually be sent on the network.
    +    """
    +    wrapped_socket = None
    +    try:
    +        wrapped_socket = _connection_refs.get(connection_id)
    +        if wrapped_socket is None:
    +            return SecurityConst.errSSLInternal
    +        base_socket = wrapped_socket.socket
    +
    +        bytes_to_write = data_length_pointer[0]
    +        data = ctypes.string_at(data_buffer, bytes_to_write)
    +
    +        timeout = wrapped_socket.gettimeout()
    +        error = None
    +        sent = 0
    +
    +        try:
    +            while sent < bytes_to_write:
    +                if timeout is None or timeout >= 0:
    +                    if not util.wait_for_write(base_socket, timeout):
    +                        raise socket.error(errno.EAGAIN, "timed out")
    +                chunk_sent = base_socket.send(data)
    +                sent += chunk_sent
    +
    +                # This has some needless copying here, but I'm not sure there's
    +                # much value in optimising this data path.
    +                data = data[chunk_sent:]
    +        except (socket.error) as e:
    +            error = e.errno
    +
    +            if error is not None and error != errno.EAGAIN:
    +                data_length_pointer[0] = sent
    +                if error == errno.ECONNRESET or error == errno.EPIPE:
    +                    return SecurityConst.errSSLClosedAbort
    +                raise
    +
    +        data_length_pointer[0] = sent
    +
    +        if sent != bytes_to_write:
    +            return SecurityConst.errSSLWouldBlock
    +
    +        return 0
    +    except Exception as e:
    +        if wrapped_socket is not None:
    +            wrapped_socket._exception = e
    +        return SecurityConst.errSSLInternal
    +
    +
    +# We need to keep these two objects references alive: if they get GC'd while
    +# in use then SecureTransport could attempt to call a function that is in freed
    +# memory. That would be...uh...bad. Yeah, that's the word. Bad.
    +_read_callback_pointer = Security.SSLReadFunc(_read_callback)
    +_write_callback_pointer = Security.SSLWriteFunc(_write_callback)
    +
    +
    +class WrappedSocket(object):
    +    """
    +    API-compatibility wrapper for Python's OpenSSL wrapped socket object.
    +
    +    Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage
    +    collector of PyPy.
    +    """
    +
    +    def __init__(self, socket):
    +        self.socket = socket
    +        self.context = None
    +        self._makefile_refs = 0
    +        self._closed = False
    +        self._exception = None
    +        self._keychain = None
    +        self._keychain_dir = None
    +        self._client_cert_chain = None
    +
    +        # We save off the previously-configured timeout and then set it to
    +        # zero. This is done because we use select and friends to handle the
    +        # timeouts, but if we leave the timeout set on the lower socket then
    +        # Python will "kindly" call select on that socket again for us. Avoid
    +        # that by forcing the timeout to zero.
    +        self._timeout = self.socket.gettimeout()
    +        self.socket.settimeout(0)
    +
    +    @contextlib.contextmanager
    +    def _raise_on_error(self):
    +        """
    +        A context manager that can be used to wrap calls that do I/O from
    +        SecureTransport. If any of the I/O callbacks hit an exception, this
    +        context manager will correctly propagate the exception after the fact.
    +        This avoids silently swallowing those exceptions.
    +
    +        It also correctly forces the socket closed.
    +        """
    +        self._exception = None
    +
    +        # We explicitly don't catch around this yield because in the unlikely
    +        # event that an exception was hit in the block we don't want to swallow
    +        # it.
    +        yield
    +        if self._exception is not None:
    +            exception, self._exception = self._exception, None
    +            self.close()
    +            raise exception
    +
    +    def _set_ciphers(self):
    +        """
    +        Sets up the allowed ciphers. By default this matches the set in
    +        util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done
    +        custom and doesn't allow changing at this time, mostly because parsing
    +        OpenSSL cipher strings is going to be a freaking nightmare.
    +        """
    +        ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES)
    +        result = Security.SSLSetEnabledCiphers(
    +            self.context, ciphers, len(CIPHER_SUITES)
    +        )
    +        _assert_no_error(result)
    +
    +    def _custom_validate(self, verify, trust_bundle):
    +        """
    +        Called when we have set custom validation. We do this in two cases:
    +        first, when cert validation is entirely disabled; and second, when
    +        using a custom trust DB.
    +        """
    +        # If we disabled cert validation, just say: cool.
    +        if not verify:
    +            return
    +
    +        # We want data in memory, so load it up.
    +        if os.path.isfile(trust_bundle):
    +            with open(trust_bundle, "rb") as f:
    +                trust_bundle = f.read()
    +
    +        cert_array = None
    +        trust = Security.SecTrustRef()
    +
    +        try:
    +            # Get a CFArray that contains the certs we want.
    +            cert_array = _cert_array_from_pem(trust_bundle)
    +
    +            # Ok, now the hard part. We want to get the SecTrustRef that ST has
    +            # created for this connection, shove our CAs into it, tell ST to
    +            # ignore everything else it knows, and then ask if it can build a
    +            # chain. This is a buuuunch of code.
    +            result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust))
    +            _assert_no_error(result)
    +            if not trust:
    +                raise ssl.SSLError("Failed to copy trust reference")
    +
    +            result = Security.SecTrustSetAnchorCertificates(trust, cert_array)
    +            _assert_no_error(result)
    +
    +            result = Security.SecTrustSetAnchorCertificatesOnly(trust, True)
    +            _assert_no_error(result)
    +
    +            trust_result = Security.SecTrustResultType()
    +            result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result))
    +            _assert_no_error(result)
    +        finally:
    +            if trust:
    +                CoreFoundation.CFRelease(trust)
    +
    +            if cert_array is not None:
    +                CoreFoundation.CFRelease(cert_array)
    +
    +        # Ok, now we can look at what the result was.
    +        successes = (
    +            SecurityConst.kSecTrustResultUnspecified,
    +            SecurityConst.kSecTrustResultProceed,
    +        )
    +        if trust_result.value not in successes:
    +            raise ssl.SSLError(
    +                "certificate verify failed, error code: %d" % trust_result.value
    +            )
    +
    +    def handshake(
    +        self,
    +        server_hostname,
    +        verify,
    +        trust_bundle,
    +        min_version,
    +        max_version,
    +        client_cert,
    +        client_key,
    +        client_key_passphrase,
    +    ):
    +        """
    +        Actually performs the TLS handshake. This is run automatically by
    +        wrapped socket, and shouldn't be needed in user code.
    +        """
    +        # First, we do the initial bits of connection setup. We need to create
    +        # a context, set its I/O funcs, and set the connection reference.
    +        self.context = Security.SSLCreateContext(
    +            None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType
    +        )
    +        result = Security.SSLSetIOFuncs(
    +            self.context, _read_callback_pointer, _write_callback_pointer
    +        )
    +        _assert_no_error(result)
    +
    +        # Here we need to compute the handle to use. We do this by taking the
    +        # id of self modulo 2**31 - 1. If this is already in the dictionary, we
    +        # just keep incrementing by one until we find a free space.
    +        with _connection_ref_lock:
    +            handle = id(self) % 2147483647
    +            while handle in _connection_refs:
    +                handle = (handle + 1) % 2147483647
    +            _connection_refs[handle] = self
    +
    +        result = Security.SSLSetConnection(self.context, handle)
    +        _assert_no_error(result)
    +
    +        # If we have a server hostname, we should set that too.
    +        if server_hostname:
    +            if not isinstance(server_hostname, bytes):
    +                server_hostname = server_hostname.encode("utf-8")
    +
    +            result = Security.SSLSetPeerDomainName(
    +                self.context, server_hostname, len(server_hostname)
    +            )
    +            _assert_no_error(result)
    +
    +        # Setup the ciphers.
    +        self._set_ciphers()
    +
    +        # Set the minimum and maximum TLS versions.
    +        result = Security.SSLSetProtocolVersionMin(self.context, min_version)
    +        _assert_no_error(result)
    +
    +        result = Security.SSLSetProtocolVersionMax(self.context, max_version)
    +        _assert_no_error(result)
    +
    +        # If there's a trust DB, we need to use it. We do that by telling
    +        # SecureTransport to break on server auth. We also do that if we don't
    +        # want to validate the certs at all: we just won't actually do any
    +        # authing in that case.
    +        if not verify or trust_bundle is not None:
    +            result = Security.SSLSetSessionOption(
    +                self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True
    +            )
    +            _assert_no_error(result)
    +
    +        # If there's a client cert, we need to use it.
    +        if client_cert:
    +            self._keychain, self._keychain_dir = _temporary_keychain()
    +            self._client_cert_chain = _load_client_cert_chain(
    +                self._keychain, client_cert, client_key
    +            )
    +            result = Security.SSLSetCertificate(self.context, self._client_cert_chain)
    +            _assert_no_error(result)
    +
    +        while True:
    +            with self._raise_on_error():
    +                result = Security.SSLHandshake(self.context)
    +
    +                if result == SecurityConst.errSSLWouldBlock:
    +                    raise socket.timeout("handshake timed out")
    +                elif result == SecurityConst.errSSLServerAuthCompleted:
    +                    self._custom_validate(verify, trust_bundle)
    +                    continue
    +                else:
    +                    _assert_no_error(result)
    +                    break
    +
    +    def fileno(self):
    +        return self.socket.fileno()
    +
    +    # Copy-pasted from Python 3.5 source code
    +    def _decref_socketios(self):
    +        if self._makefile_refs > 0:
    +            self._makefile_refs -= 1
    +        if self._closed:
    +            self.close()
    +
    +    def recv(self, bufsiz):
    +        buffer = ctypes.create_string_buffer(bufsiz)
    +        bytes_read = self.recv_into(buffer, bufsiz)
    +        data = buffer[:bytes_read]
    +        return data
    +
    +    def recv_into(self, buffer, nbytes=None):
    +        # Read short on EOF.
    +        if self._closed:
    +            return 0
    +
    +        if nbytes is None:
    +            nbytes = len(buffer)
    +
    +        buffer = (ctypes.c_char * nbytes).from_buffer(buffer)
    +        processed_bytes = ctypes.c_size_t(0)
    +
    +        with self._raise_on_error():
    +            result = Security.SSLRead(
    +                self.context, buffer, nbytes, ctypes.byref(processed_bytes)
    +            )
    +
    +        # There are some result codes that we want to treat as "not always
    +        # errors". Specifically, those are errSSLWouldBlock,
    +        # errSSLClosedGraceful, and errSSLClosedNoNotify.
    +        if result == SecurityConst.errSSLWouldBlock:
    +            # If we didn't process any bytes, then this was just a time out.
    +            # However, we can get errSSLWouldBlock in situations when we *did*
    +            # read some data, and in those cases we should just read "short"
    +            # and return.
    +            if processed_bytes.value == 0:
    +                # Timed out, no data read.
    +                raise socket.timeout("recv timed out")
    +        elif result in (
    +            SecurityConst.errSSLClosedGraceful,
    +            SecurityConst.errSSLClosedNoNotify,
    +        ):
    +            # The remote peer has closed this connection. We should do so as
    +            # well. Note that we don't actually return here because in
    +            # principle this could actually be fired along with return data.
    +            # It's unlikely though.
    +            self.close()
    +        else:
    +            _assert_no_error(result)
    +
    +        # Ok, we read and probably succeeded. We should return whatever data
    +        # was actually read.
    +        return processed_bytes.value
    +
    +    def settimeout(self, timeout):
    +        self._timeout = timeout
    +
    +    def gettimeout(self):
    +        return self._timeout
    +
    +    def send(self, data):
    +        processed_bytes = ctypes.c_size_t(0)
    +
    +        with self._raise_on_error():
    +            result = Security.SSLWrite(
    +                self.context, data, len(data), ctypes.byref(processed_bytes)
    +            )
    +
    +        if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0:
    +            # Timed out
    +            raise socket.timeout("send timed out")
    +        else:
    +            _assert_no_error(result)
    +
    +        # We sent, and probably succeeded. Tell them how much we sent.
    +        return processed_bytes.value
    +
    +    def sendall(self, data):
    +        total_sent = 0
    +        while total_sent < len(data):
    +            sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE])
    +            total_sent += sent
    +
    +    def shutdown(self):
    +        with self._raise_on_error():
    +            Security.SSLClose(self.context)
    +
    +    def close(self):
    +        # TODO: should I do clean shutdown here? Do I have to?
    +        if self._makefile_refs < 1:
    +            self._closed = True
    +            if self.context:
    +                CoreFoundation.CFRelease(self.context)
    +                self.context = None
    +            if self._client_cert_chain:
    +                CoreFoundation.CFRelease(self._client_cert_chain)
    +                self._client_cert_chain = None
    +            if self._keychain:
    +                Security.SecKeychainDelete(self._keychain)
    +                CoreFoundation.CFRelease(self._keychain)
    +                shutil.rmtree(self._keychain_dir)
    +                self._keychain = self._keychain_dir = None
    +            return self.socket.close()
    +        else:
    +            self._makefile_refs -= 1
    +
    +    def getpeercert(self, binary_form=False):
    +        # Urgh, annoying.
    +        #
    +        # Here's how we do this:
    +        #
    +        # 1. Call SSLCopyPeerTrust to get hold of the trust object for this
    +        #    connection.
    +        # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf.
    +        # 3. To get the CN, call SecCertificateCopyCommonName and process that
    +        #    string so that it's of the appropriate type.
    +        # 4. To get the SAN, we need to do something a bit more complex:
    +        #    a. Call SecCertificateCopyValues to get the data, requesting
    +        #       kSecOIDSubjectAltName.
    +        #    b. Mess about with this dictionary to try to get the SANs out.
    +        #
    +        # This is gross. Really gross. It's going to be a few hundred LoC extra
    +        # just to repeat something that SecureTransport can *already do*. So my
    +        # operating assumption at this time is that what we want to do is
    +        # instead to just flag to urllib3 that it shouldn't do its own hostname
    +        # validation when using SecureTransport.
    +        if not binary_form:
    +            raise ValueError("SecureTransport only supports dumping binary certs")
    +        trust = Security.SecTrustRef()
    +        certdata = None
    +        der_bytes = None
    +
    +        try:
    +            # Grab the trust store.
    +            result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust))
    +            _assert_no_error(result)
    +            if not trust:
    +                # Probably we haven't done the handshake yet. No biggie.
    +                return None
    +
    +            cert_count = Security.SecTrustGetCertificateCount(trust)
    +            if not cert_count:
    +                # Also a case that might happen if we haven't handshaked.
    +                # Handshook? Handshaken?
    +                return None
    +
    +            leaf = Security.SecTrustGetCertificateAtIndex(trust, 0)
    +            assert leaf
    +
    +            # Ok, now we want the DER bytes.
    +            certdata = Security.SecCertificateCopyData(leaf)
    +            assert certdata
    +
    +            data_length = CoreFoundation.CFDataGetLength(certdata)
    +            data_buffer = CoreFoundation.CFDataGetBytePtr(certdata)
    +            der_bytes = ctypes.string_at(data_buffer, data_length)
    +        finally:
    +            if certdata:
    +                CoreFoundation.CFRelease(certdata)
    +            if trust:
    +                CoreFoundation.CFRelease(trust)
    +
    +        return der_bytes
    +
    +    def version(self):
    +        protocol = Security.SSLProtocol()
    +        result = Security.SSLGetNegotiatedProtocolVersion(
    +            self.context, ctypes.byref(protocol)
    +        )
    +        _assert_no_error(result)
    +        if protocol.value == SecurityConst.kTLSProtocol13:
    +            raise ssl.SSLError("SecureTransport does not support TLS 1.3")
    +        elif protocol.value == SecurityConst.kTLSProtocol12:
    +            return "TLSv1.2"
    +        elif protocol.value == SecurityConst.kTLSProtocol11:
    +            return "TLSv1.1"
    +        elif protocol.value == SecurityConst.kTLSProtocol1:
    +            return "TLSv1"
    +        elif protocol.value == SecurityConst.kSSLProtocol3:
    +            return "SSLv3"
    +        elif protocol.value == SecurityConst.kSSLProtocol2:
    +            return "SSLv2"
    +        else:
    +            raise ssl.SSLError("Unknown TLS version: %r" % protocol)
    +
    +    def _reuse(self):
    +        self._makefile_refs += 1
    +
    +    def _drop(self):
    +        if self._makefile_refs < 1:
    +            self.close()
    +        else:
    +            self._makefile_refs -= 1
    +
    +
    +if _fileobject:  # Platform-specific: Python 2
    +
    +    def makefile(self, mode, bufsize=-1):
    +        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):
    +        # We disable buffering with SecureTransport because it conflicts with
    +        # the buffering that ST does internally (see issue #1153 for more).
    +        buffering = 0
    +        return backport_makefile(self, mode, buffering, *args, **kwargs)
    +
    +
    +WrappedSocket.makefile = makefile
    +
    +
    +class SecureTransportContext(object):
    +    """
    +    I am a wrapper class for the SecureTransport library, to translate the
    +    interface of the standard library ``SSLContext`` object to calls into
    +    SecureTransport.
    +    """
    +
    +    def __init__(self, protocol):
    +        self._min_version, self._max_version = _protocol_to_min_max[protocol]
    +        self._options = 0
    +        self._verify = False
    +        self._trust_bundle = None
    +        self._client_cert = None
    +        self._client_key = None
    +        self._client_key_passphrase = None
    +
    +    @property
    +    def check_hostname(self):
    +        """
    +        SecureTransport cannot have its hostname checking disabled. For more,
    +        see the comment on getpeercert() in this file.
    +        """
    +        return True
    +
    +    @check_hostname.setter
    +    def check_hostname(self, value):
    +        """
    +        SecureTransport cannot have its hostname checking disabled. For more,
    +        see the comment on getpeercert() in this file.
    +        """
    +        pass
    +
    +    @property
    +    def options(self):
    +        # TODO: Well, crap.
    +        #
    +        # So this is the bit of the code that is the most likely to cause us
    +        # trouble. Essentially we need to enumerate all of the SSL options that
    +        # users might want to use and try to see if we can sensibly translate
    +        # them, or whether we should just ignore them.
    +        return self._options
    +
    +    @options.setter
    +    def options(self, value):
    +        # TODO: Update in line with above.
    +        self._options = value
    +
    +    @property
    +    def verify_mode(self):
    +        return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE
    +
    +    @verify_mode.setter
    +    def verify_mode(self, value):
    +        self._verify = True if value == ssl.CERT_REQUIRED else False
    +
    +    def set_default_verify_paths(self):
    +        # So, this has to do something a bit weird. Specifically, what it does
    +        # is nothing.
    +        #
    +        # This means that, if we had previously had load_verify_locations
    +        # called, this does not undo that. We need to do that because it turns
    +        # out that the rest of the urllib3 code will attempt to load the
    +        # default verify paths if it hasn't been told about any paths, even if
    +        # the context itself was sometime earlier. We resolve that by just
    +        # ignoring it.
    +        pass
    +
    +    def load_default_certs(self):
    +        return self.set_default_verify_paths()
    +
    +    def set_ciphers(self, ciphers):
    +        # For now, we just require the default cipher string.
    +        if ciphers != util.ssl_.DEFAULT_CIPHERS:
    +            raise ValueError("SecureTransport doesn't support custom cipher strings")
    +
    +    def load_verify_locations(self, cafile=None, capath=None, cadata=None):
    +        # OK, we only really support cadata and cafile.
    +        if capath is not None:
    +            raise ValueError("SecureTransport does not support cert directories")
    +
    +        self._trust_bundle = cafile or cadata
    +
    +    def load_cert_chain(self, certfile, keyfile=None, password=None):
    +        self._client_cert = certfile
    +        self._client_key = keyfile
    +        self._client_cert_passphrase = password
    +
    +    def wrap_socket(
    +        self,
    +        sock,
    +        server_side=False,
    +        do_handshake_on_connect=True,
    +        suppress_ragged_eofs=True,
    +        server_hostname=None,
    +    ):
    +        # So, what do we do here? Firstly, we assert some properties. This is a
    +        # stripped down shim, so there is some functionality we don't support.
    +        # See PEP 543 for the real deal.
    +        assert not server_side
    +        assert do_handshake_on_connect
    +        assert suppress_ragged_eofs
    +
    +        # Ok, we're good to go. Now we want to create the wrapped socket object
    +        # and store it in the appropriate place.
    +        wrapped_socket = WrappedSocket(sock)
    +
    +        # Now we can handshake
    +        wrapped_socket.handshake(
    +            server_hostname,
    +            self._verify,
    +            self._trust_bundle,
    +            self._min_version,
    +            self._max_version,
    +            self._client_cert,
    +            self._client_key,
    +            self._client_key_passphrase,
    +        )
    +        return wrapped_socket
    diff --git a/venv/lib/python3.7/site-packages/urllib3/contrib/socks.py b/venv/lib/python3.7/site-packages/urllib3/contrib/socks.py
    new file mode 100644
    index 0000000..9e97f7a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/contrib/socks.py
    @@ -0,0 +1,210 @@
    +# -*- coding: utf-8 -*-
    +"""
    +This module contains provisional support for SOCKS proxies from within
    +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and
    +SOCKS5. To enable its functionality, either install PySocks or install this
    +module with the ``socks`` extra.
    +
    +The SOCKS implementation supports the full range of urllib3 features. It also
    +supports the following SOCKS features:
    +
    +- SOCKS4A (``proxy_url='socks4a://...``)
    +- SOCKS4 (``proxy_url='socks4://...``)
    +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``)
    +- SOCKS5 with local DNS (``proxy_url='socks5://...``)
    +- Usernames and passwords for the SOCKS proxy
    +
    + .. note::
    +    It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in
    +    your ``proxy_url`` to ensure that DNS resolution is done from the remote
    +    server instead of client-side when connecting to a domain name.
    +
    +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5
    +supports IPv4, IPv6, and domain names.
    +
    +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url``
    +will be sent as the ``userid`` section of the SOCKS request::
    +
    +    proxy_url="socks4a://@proxy-host"
    +
    +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion
    +of the ``proxy_url`` will be sent as the username/password to authenticate
    +with the proxy::
    +
    +    proxy_url="socks5h://:@proxy-host"
    +
    +"""
    +from __future__ import absolute_import
    +
    +try:
    +    import socks
    +except ImportError:
    +    import warnings
    +    from ..exceptions import DependencyWarning
    +
    +    warnings.warn(
    +        (
    +            "SOCKS support in urllib3 requires the installation of optional "
    +            "dependencies: specifically, PySocks.  For more information, see "
    +            "https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies"
    +        ),
    +        DependencyWarning,
    +    )
    +    raise
    +
    +from socket import error as SocketError, timeout as SocketTimeout
    +
    +from ..connection import HTTPConnection, HTTPSConnection
    +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool
    +from ..exceptions import ConnectTimeoutError, NewConnectionError
    +from ..poolmanager import PoolManager
    +from ..util.url import parse_url
    +
    +try:
    +    import ssl
    +except ImportError:
    +    ssl = None
    +
    +
    +class SOCKSConnection(HTTPConnection):
    +    """
    +    A plain-text HTTP connection that connects via a SOCKS proxy.
    +    """
    +
    +    def __init__(self, *args, **kwargs):
    +        self._socks_options = kwargs.pop("_socks_options")
    +        super(SOCKSConnection, self).__init__(*args, **kwargs)
    +
    +    def _new_conn(self):
    +        """
    +        Establish a new connection via the SOCKS proxy.
    +        """
    +        extra_kw = {}
    +        if self.source_address:
    +            extra_kw["source_address"] = self.source_address
    +
    +        if self.socket_options:
    +            extra_kw["socket_options"] = self.socket_options
    +
    +        try:
    +            conn = socks.create_connection(
    +                (self.host, self.port),
    +                proxy_type=self._socks_options["socks_version"],
    +                proxy_addr=self._socks_options["proxy_host"],
    +                proxy_port=self._socks_options["proxy_port"],
    +                proxy_username=self._socks_options["username"],
    +                proxy_password=self._socks_options["password"],
    +                proxy_rdns=self._socks_options["rdns"],
    +                timeout=self.timeout,
    +                **extra_kw
    +            )
    +
    +        except SocketTimeout:
    +            raise ConnectTimeoutError(
    +                self,
    +                "Connection to %s timed out. (connect timeout=%s)"
    +                % (self.host, self.timeout),
    +            )
    +
    +        except socks.ProxyError as e:
    +            # This is fragile as hell, but it seems to be the only way to raise
    +            # useful errors here.
    +            if e.socket_err:
    +                error = e.socket_err
    +                if isinstance(error, SocketTimeout):
    +                    raise ConnectTimeoutError(
    +                        self,
    +                        "Connection to %s timed out. (connect timeout=%s)"
    +                        % (self.host, self.timeout),
    +                    )
    +                else:
    +                    raise NewConnectionError(
    +                        self, "Failed to establish a new connection: %s" % error
    +                    )
    +            else:
    +                raise NewConnectionError(
    +                    self, "Failed to establish a new connection: %s" % e
    +                )
    +
    +        except SocketError as e:  # Defensive: PySocks should catch all these.
    +            raise NewConnectionError(
    +                self, "Failed to establish a new connection: %s" % e
    +            )
    +
    +        return conn
    +
    +
    +# We don't need to duplicate the Verified/Unverified distinction from
    +# urllib3/connection.py here because the HTTPSConnection will already have been
    +# correctly set to either the Verified or Unverified form by that module. This
    +# means the SOCKSHTTPSConnection will automatically be the correct type.
    +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection):
    +    pass
    +
    +
    +class SOCKSHTTPConnectionPool(HTTPConnectionPool):
    +    ConnectionCls = SOCKSConnection
    +
    +
    +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool):
    +    ConnectionCls = SOCKSHTTPSConnection
    +
    +
    +class SOCKSProxyManager(PoolManager):
    +    """
    +    A version of the urllib3 ProxyManager that routes connections via the
    +    defined SOCKS proxy.
    +    """
    +
    +    pool_classes_by_scheme = {
    +        "http": SOCKSHTTPConnectionPool,
    +        "https": SOCKSHTTPSConnectionPool,
    +    }
    +
    +    def __init__(
    +        self,
    +        proxy_url,
    +        username=None,
    +        password=None,
    +        num_pools=10,
    +        headers=None,
    +        **connection_pool_kw
    +    ):
    +        parsed = parse_url(proxy_url)
    +
    +        if username is None and password is None and parsed.auth is not None:
    +            split = parsed.auth.split(":")
    +            if len(split) == 2:
    +                username, password = split
    +        if parsed.scheme == "socks5":
    +            socks_version = socks.PROXY_TYPE_SOCKS5
    +            rdns = False
    +        elif parsed.scheme == "socks5h":
    +            socks_version = socks.PROXY_TYPE_SOCKS5
    +            rdns = True
    +        elif parsed.scheme == "socks4":
    +            socks_version = socks.PROXY_TYPE_SOCKS4
    +            rdns = False
    +        elif parsed.scheme == "socks4a":
    +            socks_version = socks.PROXY_TYPE_SOCKS4
    +            rdns = True
    +        else:
    +            raise ValueError("Unable to determine SOCKS version from %s" % proxy_url)
    +
    +        self.proxy_url = proxy_url
    +
    +        socks_options = {
    +            "socks_version": socks_version,
    +            "proxy_host": parsed.host,
    +            "proxy_port": parsed.port,
    +            "username": username,
    +            "password": password,
    +            "rdns": rdns,
    +        }
    +        connection_pool_kw["_socks_options"] = socks_options
    +
    +        super(SOCKSProxyManager, self).__init__(
    +            num_pools, headers, **connection_pool_kw
    +        )
    +
    +        self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme
    diff --git a/venv/lib/python3.7/site-packages/urllib3/exceptions.py b/venv/lib/python3.7/site-packages/urllib3/exceptions.py
    new file mode 100644
    index 0000000..0a74c79
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/exceptions.py
    @@ -0,0 +1,255 @@
    +from __future__ import absolute_import
    +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead
    +
    +# Base Exceptions
    +
    +
    +class HTTPError(Exception):
    +    "Base exception used by this module."
    +    pass
    +
    +
    +class HTTPWarning(Warning):
    +    "Base warning used by this module."
    +    pass
    +
    +
    +class PoolError(HTTPError):
    +    "Base exception for errors caused within a pool."
    +
    +    def __init__(self, pool, message):
    +        self.pool = pool
    +        HTTPError.__init__(self, "%s: %s" % (pool, message))
    +
    +    def __reduce__(self):
    +        # For pickling purposes.
    +        return self.__class__, (None, None)
    +
    +
    +class RequestError(PoolError):
    +    "Base exception for PoolErrors that have associated URLs."
    +
    +    def __init__(self, pool, url, message):
    +        self.url = url
    +        PoolError.__init__(self, pool, message)
    +
    +    def __reduce__(self):
    +        # For pickling purposes.
    +        return self.__class__, (None, self.url, None)
    +
    +
    +class SSLError(HTTPError):
    +    "Raised when SSL certificate fails in an HTTPS connection."
    +    pass
    +
    +
    +class ProxyError(HTTPError):
    +    "Raised when the connection to a proxy fails."
    +    pass
    +
    +
    +class DecodeError(HTTPError):
    +    "Raised when automatic decoding based on Content-Type fails."
    +    pass
    +
    +
    +class ProtocolError(HTTPError):
    +    "Raised when something unexpected happens mid-request/response."
    +    pass
    +
    +
    +#: Renamed to ProtocolError but aliased for backwards compatibility.
    +ConnectionError = ProtocolError
    +
    +
    +# Leaf Exceptions
    +
    +
    +class MaxRetryError(RequestError):
    +    """Raised when the maximum number of retries is exceeded.
    +
    +    :param pool: The connection pool
    +    :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool`
    +    :param string url: The requested Url
    +    :param exceptions.Exception reason: The underlying error
    +
    +    """
    +
    +    def __init__(self, pool, url, reason=None):
    +        self.reason = reason
    +
    +        message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason)
    +
    +        RequestError.__init__(self, pool, url, message)
    +
    +
    +class HostChangedError(RequestError):
    +    "Raised when an existing pool gets a request for a foreign host."
    +
    +    def __init__(self, pool, url, retries=3):
    +        message = "Tried to open a foreign host with url: %s" % url
    +        RequestError.__init__(self, pool, url, message)
    +        self.retries = retries
    +
    +
    +class TimeoutStateError(HTTPError):
    +    """ Raised when passing an invalid state to a timeout """
    +
    +    pass
    +
    +
    +class TimeoutError(HTTPError):
    +    """ Raised when a socket timeout error occurs.
    +
    +    Catching this error will catch both :exc:`ReadTimeoutErrors
    +    ` and :exc:`ConnectTimeoutErrors `.
    +    """
    +
    +    pass
    +
    +
    +class ReadTimeoutError(TimeoutError, RequestError):
    +    "Raised when a socket timeout occurs while receiving data from a server"
    +    pass
    +
    +
    +# This timeout error does not have a URL attached and needs to inherit from the
    +# base HTTPError
    +class ConnectTimeoutError(TimeoutError):
    +    "Raised when a socket timeout occurs while connecting to a server"
    +    pass
    +
    +
    +class NewConnectionError(ConnectTimeoutError, PoolError):
    +    "Raised when we fail to establish a new connection. Usually ECONNREFUSED."
    +    pass
    +
    +
    +class EmptyPoolError(PoolError):
    +    "Raised when a pool runs out of connections and no more are allowed."
    +    pass
    +
    +
    +class ClosedPoolError(PoolError):
    +    "Raised when a request enters a pool after the pool has been closed."
    +    pass
    +
    +
    +class LocationValueError(ValueError, HTTPError):
    +    "Raised when there is something wrong with a given URL input."
    +    pass
    +
    +
    +class LocationParseError(LocationValueError):
    +    "Raised when get_host or similar fails to parse the URL input."
    +
    +    def __init__(self, location):
    +        message = "Failed to parse: %s" % location
    +        HTTPError.__init__(self, message)
    +
    +        self.location = location
    +
    +
    +class ResponseError(HTTPError):
    +    "Used as a container for an error reason supplied in a MaxRetryError."
    +    GENERIC_ERROR = "too many error responses"
    +    SPECIFIC_ERROR = "too many {status_code} error responses"
    +
    +
    +class SecurityWarning(HTTPWarning):
    +    "Warned when performing security reducing actions"
    +    pass
    +
    +
    +class SubjectAltNameWarning(SecurityWarning):
    +    "Warned when connecting to a host with a certificate missing a SAN."
    +    pass
    +
    +
    +class InsecureRequestWarning(SecurityWarning):
    +    "Warned when making an unverified HTTPS request."
    +    pass
    +
    +
    +class SystemTimeWarning(SecurityWarning):
    +    "Warned when system time is suspected to be wrong"
    +    pass
    +
    +
    +class InsecurePlatformWarning(SecurityWarning):
    +    "Warned when certain SSL configuration is not available on a platform."
    +    pass
    +
    +
    +class SNIMissingWarning(HTTPWarning):
    +    "Warned when making a HTTPS request without SNI available."
    +    pass
    +
    +
    +class DependencyWarning(HTTPWarning):
    +    """
    +    Warned when an attempt is made to import a module with missing optional
    +    dependencies.
    +    """
    +
    +    pass
    +
    +
    +class ResponseNotChunked(ProtocolError, ValueError):
    +    "Response needs to be chunked in order to read it as chunks."
    +    pass
    +
    +
    +class BodyNotHttplibCompatible(HTTPError):
    +    """
    +    Body should be httplib.HTTPResponse like (have an fp attribute which
    +    returns raw chunks) for read_chunked().
    +    """
    +
    +    pass
    +
    +
    +class IncompleteRead(HTTPError, httplib_IncompleteRead):
    +    """
    +    Response length doesn't match expected Content-Length
    +
    +    Subclass of http_client.IncompleteRead to allow int value
    +    for `partial` to avoid creating large objects on streamed
    +    reads.
    +    """
    +
    +    def __init__(self, partial, expected):
    +        super(IncompleteRead, self).__init__(partial, expected)
    +
    +    def __repr__(self):
    +        return "IncompleteRead(%i bytes read, %i more expected)" % (
    +            self.partial,
    +            self.expected,
    +        )
    +
    +
    +class InvalidHeader(HTTPError):
    +    "The header provided was somehow invalid."
    +    pass
    +
    +
    +class ProxySchemeUnknown(AssertionError, ValueError):
    +    "ProxyManager does not support the supplied scheme"
    +    # TODO(t-8ch): Stop inheriting from AssertionError in v2.0.
    +
    +    def __init__(self, scheme):
    +        message = "Not supported proxy scheme %s" % scheme
    +        super(ProxySchemeUnknown, self).__init__(message)
    +
    +
    +class HeaderParsingError(HTTPError):
    +    "Raised by assert_header_parsing, but we convert it to a log.warning statement."
    +
    +    def __init__(self, defects, unparsed_data):
    +        message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data)
    +        super(HeaderParsingError, self).__init__(message)
    +
    +
    +class UnrewindableBodyError(HTTPError):
    +    "urllib3 encountered an error when trying to rewind a body"
    +    pass
    diff --git a/venv/lib/python3.7/site-packages/urllib3/fields.py b/venv/lib/python3.7/site-packages/urllib3/fields.py
    new file mode 100644
    index 0000000..8715b22
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/fields.py
    @@ -0,0 +1,273 @@
    +from __future__ import absolute_import
    +import email.utils
    +import mimetypes
    +import re
    +
    +from .packages import six
    +
    +
    +def guess_content_type(filename, default="application/octet-stream"):
    +    """
    +    Guess the "Content-Type" of a file.
    +
    +    :param filename:
    +        The filename to guess the "Content-Type" of using :mod:`mimetypes`.
    +    :param default:
    +        If no "Content-Type" can be guessed, default to `default`.
    +    """
    +    if filename:
    +        return mimetypes.guess_type(filename)[0] or default
    +    return default
    +
    +
    +def format_header_param_rfc2231(name, value):
    +    """
    +    Helper function to format and quote a single header parameter using the
    +    strategy defined in RFC 2231.
    +
    +    Particularly useful for header parameters which might contain
    +    non-ASCII values, like file names. This follows RFC 2388 Section 4.4.
    +
    +    :param name:
    +        The name of the parameter, a string expected to be ASCII only.
    +    :param value:
    +        The value of the parameter, provided as ``bytes`` or `str``.
    +    :ret:
    +        An RFC-2231-formatted unicode string.
    +    """
    +    if isinstance(value, six.binary_type):
    +        value = value.decode("utf-8")
    +
    +    if not any(ch in value for ch in '"\\\r\n'):
    +        result = u'%s="%s"' % (name, value)
    +        try:
    +            result.encode("ascii")
    +        except (UnicodeEncodeError, UnicodeDecodeError):
    +            pass
    +        else:
    +            return result
    +
    +    if six.PY2:  # Python 2:
    +        value = value.encode("utf-8")
    +
    +    # encode_rfc2231 accepts an encoded string and returns an ascii-encoded
    +    # string in Python 2 but accepts and returns unicode strings in Python 3
    +    value = email.utils.encode_rfc2231(value, "utf-8")
    +    value = "%s*=%s" % (name, value)
    +
    +    if six.PY2:  # Python 2:
    +        value = value.decode("utf-8")
    +
    +    return value
    +
    +
    +_HTML5_REPLACEMENTS = {
    +    u"\u0022": u"%22",
    +    # Replace "\" with "\\".
    +    u"\u005C": u"\u005C\u005C",
    +    u"\u005C": u"\u005C\u005C",
    +}
    +
    +# All control characters from 0x00 to 0x1F *except* 0x1B.
    +_HTML5_REPLACEMENTS.update(
    +    {
    +        six.unichr(cc): u"%{:02X}".format(cc)
    +        for cc in range(0x00, 0x1F + 1)
    +        if cc not in (0x1B,)
    +    }
    +)
    +
    +
    +def _replace_multiple(value, needles_and_replacements):
    +    def replacer(match):
    +        return needles_and_replacements[match.group(0)]
    +
    +    pattern = re.compile(
    +        r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()])
    +    )
    +
    +    result = pattern.sub(replacer, value)
    +
    +    return result
    +
    +
    +def format_header_param_html5(name, value):
    +    """
    +    Helper function to format and quote a single header parameter using the
    +    HTML5 strategy.
    +
    +    Particularly useful for header parameters which might contain
    +    non-ASCII values, like file names. This follows the `HTML5 Working Draft
    +    Section 4.10.22.7`_ and matches the behavior of curl and modern browsers.
    +
    +    .. _HTML5 Working Draft Section 4.10.22.7:
    +        https://w3c.github.io/html/sec-forms.html#multipart-form-data
    +
    +    :param name:
    +        The name of the parameter, a string expected to be ASCII only.
    +    :param value:
    +        The value of the parameter, provided as ``bytes`` or `str``.
    +    :ret:
    +        A unicode string, stripped of troublesome characters.
    +    """
    +    if isinstance(value, six.binary_type):
    +        value = value.decode("utf-8")
    +
    +    value = _replace_multiple(value, _HTML5_REPLACEMENTS)
    +
    +    return u'%s="%s"' % (name, value)
    +
    +
    +# For backwards-compatibility.
    +format_header_param = format_header_param_html5
    +
    +
    +class RequestField(object):
    +    """
    +    A data container for request body parameters.
    +
    +    :param name:
    +        The name of this request field. Must be unicode.
    +    :param data:
    +        The data/value body.
    +    :param filename:
    +        An optional filename of the request field. Must be unicode.
    +    :param headers:
    +        An optional dict-like object of headers to initially use for the field.
    +    :param header_formatter:
    +        An optional callable that is used to encode and format the headers. By
    +        default, this is :func:`format_header_param_html5`.
    +    """
    +
    +    def __init__(
    +        self,
    +        name,
    +        data,
    +        filename=None,
    +        headers=None,
    +        header_formatter=format_header_param_html5,
    +    ):
    +        self._name = name
    +        self._filename = filename
    +        self.data = data
    +        self.headers = {}
    +        if headers:
    +            self.headers = dict(headers)
    +        self.header_formatter = header_formatter
    +
    +    @classmethod
    +    def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5):
    +        """
    +        A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters.
    +
    +        Supports constructing :class:`~urllib3.fields.RequestField` from
    +        parameter of key/value strings AND key/filetuple. A filetuple is a
    +        (filename, data, MIME type) tuple where the MIME type is optional.
    +        For example::
    +
    +            'foo': 'bar',
    +            'fakefile': ('foofile.txt', 'contents of foofile'),
    +            'realfile': ('barfile.txt', open('realfile').read()),
    +            'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'),
    +            'nonamefile': 'contents of nonamefile field',
    +
    +        Field names and filenames must be unicode.
    +        """
    +        if isinstance(value, tuple):
    +            if len(value) == 3:
    +                filename, data, content_type = value
    +            else:
    +                filename, data = value
    +                content_type = guess_content_type(filename)
    +        else:
    +            filename = None
    +            content_type = None
    +            data = value
    +
    +        request_param = cls(
    +            fieldname, data, filename=filename, header_formatter=header_formatter
    +        )
    +        request_param.make_multipart(content_type=content_type)
    +
    +        return request_param
    +
    +    def _render_part(self, name, value):
    +        """
    +        Overridable helper function to format a single header parameter. By
    +        default, this calls ``self.header_formatter``.
    +
    +        :param name:
    +            The name of the parameter, a string expected to be ASCII only.
    +        :param value:
    +            The value of the parameter, provided as a unicode string.
    +        """
    +
    +        return self.header_formatter(name, value)
    +
    +    def _render_parts(self, header_parts):
    +        """
    +        Helper function to format and quote a single header.
    +
    +        Useful for single headers that are composed of multiple items. E.g.,
    +        'Content-Disposition' fields.
    +
    +        :param header_parts:
    +            A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format
    +            as `k1="v1"; k2="v2"; ...`.
    +        """
    +        parts = []
    +        iterable = header_parts
    +        if isinstance(header_parts, dict):
    +            iterable = header_parts.items()
    +
    +        for name, value in iterable:
    +            if value is not None:
    +                parts.append(self._render_part(name, value))
    +
    +        return u"; ".join(parts)
    +
    +    def render_headers(self):
    +        """
    +        Renders the headers for this request field.
    +        """
    +        lines = []
    +
    +        sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"]
    +        for sort_key in sort_keys:
    +            if self.headers.get(sort_key, False):
    +                lines.append(u"%s: %s" % (sort_key, self.headers[sort_key]))
    +
    +        for header_name, header_value in self.headers.items():
    +            if header_name not in sort_keys:
    +                if header_value:
    +                    lines.append(u"%s: %s" % (header_name, header_value))
    +
    +        lines.append(u"\r\n")
    +        return u"\r\n".join(lines)
    +
    +    def make_multipart(
    +        self, content_disposition=None, content_type=None, content_location=None
    +    ):
    +        """
    +        Makes this request field into a multipart request field.
    +
    +        This method overrides "Content-Disposition", "Content-Type" and
    +        "Content-Location" headers to the request parameter.
    +
    +        :param content_type:
    +            The 'Content-Type' of the request body.
    +        :param content_location:
    +            The 'Content-Location' of the request body.
    +
    +        """
    +        self.headers["Content-Disposition"] = content_disposition or u"form-data"
    +        self.headers["Content-Disposition"] += u"; ".join(
    +            [
    +                u"",
    +                self._render_parts(
    +                    ((u"name", self._name), (u"filename", self._filename))
    +                ),
    +            ]
    +        )
    +        self.headers["Content-Type"] = content_type
    +        self.headers["Content-Location"] = content_location
    diff --git a/venv/lib/python3.7/site-packages/urllib3/filepost.py b/venv/lib/python3.7/site-packages/urllib3/filepost.py
    new file mode 100644
    index 0000000..b7b0099
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/filepost.py
    @@ -0,0 +1,98 @@
    +from __future__ import absolute_import
    +import binascii
    +import codecs
    +import os
    +
    +from io import BytesIO
    +
    +from .packages import six
    +from .packages.six import b
    +from .fields import RequestField
    +
    +writer = codecs.lookup("utf-8")[3]
    +
    +
    +def choose_boundary():
    +    """
    +    Our embarrassingly-simple replacement for mimetools.choose_boundary.
    +    """
    +    boundary = binascii.hexlify(os.urandom(16))
    +    if not six.PY2:
    +        boundary = boundary.decode("ascii")
    +    return boundary
    +
    +
    +def iter_field_objects(fields):
    +    """
    +    Iterate over fields.
    +
    +    Supports list of (k, v) tuples and dicts, and lists of
    +    :class:`~urllib3.fields.RequestField`.
    +
    +    """
    +    if isinstance(fields, dict):
    +        i = six.iteritems(fields)
    +    else:
    +        i = iter(fields)
    +
    +    for field in i:
    +        if isinstance(field, RequestField):
    +            yield field
    +        else:
    +            yield RequestField.from_tuples(*field)
    +
    +
    +def iter_fields(fields):
    +    """
    +    .. deprecated:: 1.6
    +
    +    Iterate over fields.
    +
    +    The addition of :class:`~urllib3.fields.RequestField` makes this function
    +    obsolete. Instead, use :func:`iter_field_objects`, which returns
    +    :class:`~urllib3.fields.RequestField` objects.
    +
    +    Supports list of (k, v) tuples and dicts.
    +    """
    +    if isinstance(fields, dict):
    +        return ((k, v) for k, v in six.iteritems(fields))
    +
    +    return ((k, v) for k, v in fields)
    +
    +
    +def encode_multipart_formdata(fields, boundary=None):
    +    """
    +    Encode a dictionary of ``fields`` using the multipart/form-data MIME format.
    +
    +    :param fields:
    +        Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`).
    +
    +    :param boundary:
    +        If not specified, then a random boundary will be generated using
    +        :func:`urllib3.filepost.choose_boundary`.
    +    """
    +    body = BytesIO()
    +    if boundary is None:
    +        boundary = choose_boundary()
    +
    +    for field in iter_field_objects(fields):
    +        body.write(b("--%s\r\n" % (boundary)))
    +
    +        writer(body).write(field.render_headers())
    +        data = field.data
    +
    +        if isinstance(data, int):
    +            data = str(data)  # Backwards compatibility
    +
    +        if isinstance(data, six.text_type):
    +            writer(body).write(data)
    +        else:
    +            body.write(data)
    +
    +        body.write(b"\r\n")
    +
    +    body.write(b("--%s--\r\n" % (boundary)))
    +
    +    content_type = str("multipart/form-data; boundary=%s" % boundary)
    +
    +    return body.getvalue(), content_type
    diff --git a/venv/lib/python3.7/site-packages/urllib3/packages/__init__.py b/venv/lib/python3.7/site-packages/urllib3/packages/__init__.py
    new file mode 100644
    index 0000000..fce4caa
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/packages/__init__.py
    @@ -0,0 +1,5 @@
    +from __future__ import absolute_import
    +
    +from . import ssl_match_hostname
    +
    +__all__ = ("ssl_match_hostname",)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/packages/backports/__init__.py b/venv/lib/python3.7/site-packages/urllib3/packages/backports/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/venv/lib/python3.7/site-packages/urllib3/packages/backports/makefile.py b/venv/lib/python3.7/site-packages/urllib3/packages/backports/makefile.py
    new file mode 100644
    index 0000000..a3156a6
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/packages/backports/makefile.py
    @@ -0,0 +1,52 @@
    +# -*- coding: utf-8 -*-
    +"""
    +backports.makefile
    +~~~~~~~~~~~~~~~~~~
    +
    +Backports the Python 3 ``socket.makefile`` method for use with anything that
    +wants to create a "fake" socket object.
    +"""
    +import io
    +
    +from socket import SocketIO
    +
    +
    +def backport_makefile(
    +    self, mode="r", buffering=None, encoding=None, errors=None, newline=None
    +):
    +    """
    +    Backport of ``socket.makefile`` from Python 3.5.
    +    """
    +    if not set(mode) <= {"r", "w", "b"}:
    +        raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,))
    +    writing = "w" in mode
    +    reading = "r" in mode or not writing
    +    assert reading or writing
    +    binary = "b" in mode
    +    rawmode = ""
    +    if reading:
    +        rawmode += "r"
    +    if writing:
    +        rawmode += "w"
    +    raw = SocketIO(self, rawmode)
    +    self._makefile_refs += 1
    +    if buffering is None:
    +        buffering = -1
    +    if buffering < 0:
    +        buffering = io.DEFAULT_BUFFER_SIZE
    +    if buffering == 0:
    +        if not binary:
    +            raise ValueError("unbuffered streams must be binary")
    +        return raw
    +    if reading and writing:
    +        buffer = io.BufferedRWPair(raw, raw, buffering)
    +    elif reading:
    +        buffer = io.BufferedReader(raw, buffering)
    +    else:
    +        assert writing
    +        buffer = io.BufferedWriter(raw, buffering)
    +    if binary:
    +        return buffer
    +    text = io.TextIOWrapper(buffer, encoding, errors, newline)
    +    text.mode = mode
    +    return text
    diff --git a/venv/lib/python3.7/site-packages/urllib3/packages/six.py b/venv/lib/python3.7/site-packages/urllib3/packages/six.py
    new file mode 100644
    index 0000000..3144240
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/packages/six.py
    @@ -0,0 +1,1021 @@
    +# Copyright (c) 2010-2019 Benjamin Peterson
    +#
    +# 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.
    +
    +"""Utilities for writing code that runs on Python 2 and 3"""
    +
    +from __future__ import absolute_import
    +
    +import functools
    +import itertools
    +import operator
    +import sys
    +import types
    +
    +__author__ = "Benjamin Peterson "
    +__version__ = "1.12.0"
    +
    +
    +# Useful for very coarse version differentiation.
    +PY2 = sys.version_info[0] == 2
    +PY3 = sys.version_info[0] == 3
    +PY34 = sys.version_info[0:2] >= (3, 4)
    +
    +if PY3:
    +    string_types = (str,)
    +    integer_types = (int,)
    +    class_types = (type,)
    +    text_type = str
    +    binary_type = bytes
    +
    +    MAXSIZE = sys.maxsize
    +else:
    +    string_types = (basestring,)
    +    integer_types = (int, long)
    +    class_types = (type, types.ClassType)
    +    text_type = unicode
    +    binary_type = str
    +
    +    if sys.platform.startswith("java"):
    +        # Jython always uses 32 bits.
    +        MAXSIZE = int((1 << 31) - 1)
    +    else:
    +        # It's possible to have sizeof(long) != sizeof(Py_ssize_t).
    +        class X(object):
    +            def __len__(self):
    +                return 1 << 31
    +
    +        try:
    +            len(X())
    +        except OverflowError:
    +            # 32-bit
    +            MAXSIZE = int((1 << 31) - 1)
    +        else:
    +            # 64-bit
    +            MAXSIZE = int((1 << 63) - 1)
    +        del X
    +
    +
    +def _add_doc(func, doc):
    +    """Add documentation to a function."""
    +    func.__doc__ = doc
    +
    +
    +def _import_module(name):
    +    """Import module, returning the module after the last dot."""
    +    __import__(name)
    +    return sys.modules[name]
    +
    +
    +class _LazyDescr(object):
    +    def __init__(self, name):
    +        self.name = name
    +
    +    def __get__(self, obj, tp):
    +        result = self._resolve()
    +        setattr(obj, self.name, result)  # Invokes __set__.
    +        try:
    +            # This is a bit ugly, but it avoids running this again by
    +            # removing this descriptor.
    +            delattr(obj.__class__, self.name)
    +        except AttributeError:
    +            pass
    +        return result
    +
    +
    +class MovedModule(_LazyDescr):
    +    def __init__(self, name, old, new=None):
    +        super(MovedModule, self).__init__(name)
    +        if PY3:
    +            if new is None:
    +                new = name
    +            self.mod = new
    +        else:
    +            self.mod = old
    +
    +    def _resolve(self):
    +        return _import_module(self.mod)
    +
    +    def __getattr__(self, attr):
    +        _module = self._resolve()
    +        value = getattr(_module, attr)
    +        setattr(self, attr, value)
    +        return value
    +
    +
    +class _LazyModule(types.ModuleType):
    +    def __init__(self, name):
    +        super(_LazyModule, self).__init__(name)
    +        self.__doc__ = self.__class__.__doc__
    +
    +    def __dir__(self):
    +        attrs = ["__doc__", "__name__"]
    +        attrs += [attr.name for attr in self._moved_attributes]
    +        return attrs
    +
    +    # Subclasses should override this
    +    _moved_attributes = []
    +
    +
    +class MovedAttribute(_LazyDescr):
    +    def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
    +        super(MovedAttribute, self).__init__(name)
    +        if PY3:
    +            if new_mod is None:
    +                new_mod = name
    +            self.mod = new_mod
    +            if new_attr is None:
    +                if old_attr is None:
    +                    new_attr = name
    +                else:
    +                    new_attr = old_attr
    +            self.attr = new_attr
    +        else:
    +            self.mod = old_mod
    +            if old_attr is None:
    +                old_attr = name
    +            self.attr = old_attr
    +
    +    def _resolve(self):
    +        module = _import_module(self.mod)
    +        return getattr(module, self.attr)
    +
    +
    +class _SixMetaPathImporter(object):
    +
    +    """
    +    A meta path importer to import six.moves and its submodules.
    +
    +    This class implements a PEP302 finder and loader. It should be compatible
    +    with Python 2.5 and all existing versions of Python3
    +    """
    +
    +    def __init__(self, six_module_name):
    +        self.name = six_module_name
    +        self.known_modules = {}
    +
    +    def _add_module(self, mod, *fullnames):
    +        for fullname in fullnames:
    +            self.known_modules[self.name + "." + fullname] = mod
    +
    +    def _get_module(self, fullname):
    +        return self.known_modules[self.name + "." + fullname]
    +
    +    def find_module(self, fullname, path=None):
    +        if fullname in self.known_modules:
    +            return self
    +        return None
    +
    +    def __get_module(self, fullname):
    +        try:
    +            return self.known_modules[fullname]
    +        except KeyError:
    +            raise ImportError("This loader does not know module " + fullname)
    +
    +    def load_module(self, fullname):
    +        try:
    +            # in case of a reload
    +            return sys.modules[fullname]
    +        except KeyError:
    +            pass
    +        mod = self.__get_module(fullname)
    +        if isinstance(mod, MovedModule):
    +            mod = mod._resolve()
    +        else:
    +            mod.__loader__ = self
    +        sys.modules[fullname] = mod
    +        return mod
    +
    +    def is_package(self, fullname):
    +        """
    +        Return true, if the named module is a package.
    +
    +        We need this method to get correct spec objects with
    +        Python 3.4 (see PEP451)
    +        """
    +        return hasattr(self.__get_module(fullname), "__path__")
    +
    +    def get_code(self, fullname):
    +        """Return None
    +
    +        Required, if is_package is implemented"""
    +        self.__get_module(fullname)  # eventually raises ImportError
    +        return None
    +
    +    get_source = get_code  # same as get_code
    +
    +
    +_importer = _SixMetaPathImporter(__name__)
    +
    +
    +class _MovedItems(_LazyModule):
    +
    +    """Lazy loading of moved objects"""
    +
    +    __path__ = []  # mark as package
    +
    +
    +_moved_attributes = [
    +    MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
    +    MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
    +    MovedAttribute(
    +        "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"
    +    ),
    +    MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
    +    MovedAttribute("intern", "__builtin__", "sys"),
    +    MovedAttribute("map", "itertools", "builtins", "imap", "map"),
    +    MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
    +    MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
    +    MovedAttribute("getoutput", "commands", "subprocess"),
    +    MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
    +    MovedAttribute(
    +        "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"
    +    ),
    +    MovedAttribute("reduce", "__builtin__", "functools"),
    +    MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
    +    MovedAttribute("StringIO", "StringIO", "io"),
    +    MovedAttribute("UserDict", "UserDict", "collections"),
    +    MovedAttribute("UserList", "UserList", "collections"),
    +    MovedAttribute("UserString", "UserString", "collections"),
    +    MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
    +    MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
    +    MovedAttribute(
    +        "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"
    +    ),
    +    MovedModule("builtins", "__builtin__"),
    +    MovedModule("configparser", "ConfigParser"),
    +    MovedModule("copyreg", "copy_reg"),
    +    MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
    +    MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
    +    MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
    +    MovedModule("http_cookies", "Cookie", "http.cookies"),
    +    MovedModule("html_entities", "htmlentitydefs", "html.entities"),
    +    MovedModule("html_parser", "HTMLParser", "html.parser"),
    +    MovedModule("http_client", "httplib", "http.client"),
    +    MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
    +    MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
    +    MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
    +    MovedModule(
    +        "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"
    +    ),
    +    MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
    +    MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
    +    MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
    +    MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
    +    MovedModule("cPickle", "cPickle", "pickle"),
    +    MovedModule("queue", "Queue"),
    +    MovedModule("reprlib", "repr"),
    +    MovedModule("socketserver", "SocketServer"),
    +    MovedModule("_thread", "thread", "_thread"),
    +    MovedModule("tkinter", "Tkinter"),
    +    MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
    +    MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
    +    MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
    +    MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
    +    MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
    +    MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
    +    MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
    +    MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
    +    MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"),
    +    MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"),
    +    MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
    +    MovedModule("tkinter_font", "tkFont", "tkinter.font"),
    +    MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
    +    MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"),
    +    MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
    +    MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
    +    MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
    +    MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
    +    MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
    +    MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
    +]
    +# Add windows specific modules.
    +if sys.platform == "win32":
    +    _moved_attributes += [MovedModule("winreg", "_winreg")]
    +
    +for attr in _moved_attributes:
    +    setattr(_MovedItems, attr.name, attr)
    +    if isinstance(attr, MovedModule):
    +        _importer._add_module(attr, "moves." + attr.name)
    +del attr
    +
    +_MovedItems._moved_attributes = _moved_attributes
    +
    +moves = _MovedItems(__name__ + ".moves")
    +_importer._add_module(moves, "moves")
    +
    +
    +class Module_six_moves_urllib_parse(_LazyModule):
    +
    +    """Lazy loading of moved objects in six.moves.urllib_parse"""
    +
    +
    +_urllib_parse_moved_attributes = [
    +    MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
    +    MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
    +    MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
    +    MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
    +    MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
    +    MovedAttribute("urljoin", "urlparse", "urllib.parse"),
    +    MovedAttribute("urlparse", "urlparse", "urllib.parse"),
    +    MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
    +    MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
    +    MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
    +    MovedAttribute("quote", "urllib", "urllib.parse"),
    +    MovedAttribute("quote_plus", "urllib", "urllib.parse"),
    +    MovedAttribute("unquote", "urllib", "urllib.parse"),
    +    MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
    +    MovedAttribute(
    +        "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"
    +    ),
    +    MovedAttribute("urlencode", "urllib", "urllib.parse"),
    +    MovedAttribute("splitquery", "urllib", "urllib.parse"),
    +    MovedAttribute("splittag", "urllib", "urllib.parse"),
    +    MovedAttribute("splituser", "urllib", "urllib.parse"),
    +    MovedAttribute("splitvalue", "urllib", "urllib.parse"),
    +    MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
    +    MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
    +    MovedAttribute("uses_params", "urlparse", "urllib.parse"),
    +    MovedAttribute("uses_query", "urlparse", "urllib.parse"),
    +    MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
    +]
    +for attr in _urllib_parse_moved_attributes:
    +    setattr(Module_six_moves_urllib_parse, attr.name, attr)
    +del attr
    +
    +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
    +
    +_importer._add_module(
    +    Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
    +    "moves.urllib_parse",
    +    "moves.urllib.parse",
    +)
    +
    +
    +class Module_six_moves_urllib_error(_LazyModule):
    +
    +    """Lazy loading of moved objects in six.moves.urllib_error"""
    +
    +
    +_urllib_error_moved_attributes = [
    +    MovedAttribute("URLError", "urllib2", "urllib.error"),
    +    MovedAttribute("HTTPError", "urllib2", "urllib.error"),
    +    MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
    +]
    +for attr in _urllib_error_moved_attributes:
    +    setattr(Module_six_moves_urllib_error, attr.name, attr)
    +del attr
    +
    +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
    +
    +_importer._add_module(
    +    Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
    +    "moves.urllib_error",
    +    "moves.urllib.error",
    +)
    +
    +
    +class Module_six_moves_urllib_request(_LazyModule):
    +
    +    """Lazy loading of moved objects in six.moves.urllib_request"""
    +
    +
    +_urllib_request_moved_attributes = [
    +    MovedAttribute("urlopen", "urllib2", "urllib.request"),
    +    MovedAttribute("install_opener", "urllib2", "urllib.request"),
    +    MovedAttribute("build_opener", "urllib2", "urllib.request"),
    +    MovedAttribute("pathname2url", "urllib", "urllib.request"),
    +    MovedAttribute("url2pathname", "urllib", "urllib.request"),
    +    MovedAttribute("getproxies", "urllib", "urllib.request"),
    +    MovedAttribute("Request", "urllib2", "urllib.request"),
    +    MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
    +    MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
    +    MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("FileHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
    +    MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
    +    MovedAttribute("urlretrieve", "urllib", "urllib.request"),
    +    MovedAttribute("urlcleanup", "urllib", "urllib.request"),
    +    MovedAttribute("URLopener", "urllib", "urllib.request"),
    +    MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
    +    MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
    +    MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
    +    MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
    +]
    +for attr in _urllib_request_moved_attributes:
    +    setattr(Module_six_moves_urllib_request, attr.name, attr)
    +del attr
    +
    +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
    +
    +_importer._add_module(
    +    Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
    +    "moves.urllib_request",
    +    "moves.urllib.request",
    +)
    +
    +
    +class Module_six_moves_urllib_response(_LazyModule):
    +
    +    """Lazy loading of moved objects in six.moves.urllib_response"""
    +
    +
    +_urllib_response_moved_attributes = [
    +    MovedAttribute("addbase", "urllib", "urllib.response"),
    +    MovedAttribute("addclosehook", "urllib", "urllib.response"),
    +    MovedAttribute("addinfo", "urllib", "urllib.response"),
    +    MovedAttribute("addinfourl", "urllib", "urllib.response"),
    +]
    +for attr in _urllib_response_moved_attributes:
    +    setattr(Module_six_moves_urllib_response, attr.name, attr)
    +del attr
    +
    +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
    +
    +_importer._add_module(
    +    Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
    +    "moves.urllib_response",
    +    "moves.urllib.response",
    +)
    +
    +
    +class Module_six_moves_urllib_robotparser(_LazyModule):
    +
    +    """Lazy loading of moved objects in six.moves.urllib_robotparser"""
    +
    +
    +_urllib_robotparser_moved_attributes = [
    +    MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser")
    +]
    +for attr in _urllib_robotparser_moved_attributes:
    +    setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
    +del attr
    +
    +Module_six_moves_urllib_robotparser._moved_attributes = (
    +    _urllib_robotparser_moved_attributes
    +)
    +
    +_importer._add_module(
    +    Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
    +    "moves.urllib_robotparser",
    +    "moves.urllib.robotparser",
    +)
    +
    +
    +class Module_six_moves_urllib(types.ModuleType):
    +
    +    """Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
    +
    +    __path__ = []  # mark as package
    +    parse = _importer._get_module("moves.urllib_parse")
    +    error = _importer._get_module("moves.urllib_error")
    +    request = _importer._get_module("moves.urllib_request")
    +    response = _importer._get_module("moves.urllib_response")
    +    robotparser = _importer._get_module("moves.urllib_robotparser")
    +
    +    def __dir__(self):
    +        return ["parse", "error", "request", "response", "robotparser"]
    +
    +
    +_importer._add_module(
    +    Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib"
    +)
    +
    +
    +def add_move(move):
    +    """Add an item to six.moves."""
    +    setattr(_MovedItems, move.name, move)
    +
    +
    +def remove_move(name):
    +    """Remove item from six.moves."""
    +    try:
    +        delattr(_MovedItems, name)
    +    except AttributeError:
    +        try:
    +            del moves.__dict__[name]
    +        except KeyError:
    +            raise AttributeError("no such move, %r" % (name,))
    +
    +
    +if PY3:
    +    _meth_func = "__func__"
    +    _meth_self = "__self__"
    +
    +    _func_closure = "__closure__"
    +    _func_code = "__code__"
    +    _func_defaults = "__defaults__"
    +    _func_globals = "__globals__"
    +else:
    +    _meth_func = "im_func"
    +    _meth_self = "im_self"
    +
    +    _func_closure = "func_closure"
    +    _func_code = "func_code"
    +    _func_defaults = "func_defaults"
    +    _func_globals = "func_globals"
    +
    +
    +try:
    +    advance_iterator = next
    +except NameError:
    +
    +    def advance_iterator(it):
    +        return it.next()
    +
    +
    +next = advance_iterator
    +
    +
    +try:
    +    callable = callable
    +except NameError:
    +
    +    def callable(obj):
    +        return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
    +
    +
    +if PY3:
    +
    +    def get_unbound_function(unbound):
    +        return unbound
    +
    +    create_bound_method = types.MethodType
    +
    +    def create_unbound_method(func, cls):
    +        return func
    +
    +    Iterator = object
    +else:
    +
    +    def get_unbound_function(unbound):
    +        return unbound.im_func
    +
    +    def create_bound_method(func, obj):
    +        return types.MethodType(func, obj, obj.__class__)
    +
    +    def create_unbound_method(func, cls):
    +        return types.MethodType(func, None, cls)
    +
    +    class Iterator(object):
    +        def next(self):
    +            return type(self).__next__(self)
    +
    +    callable = callable
    +_add_doc(
    +    get_unbound_function, """Get the function out of a possibly unbound function"""
    +)
    +
    +
    +get_method_function = operator.attrgetter(_meth_func)
    +get_method_self = operator.attrgetter(_meth_self)
    +get_function_closure = operator.attrgetter(_func_closure)
    +get_function_code = operator.attrgetter(_func_code)
    +get_function_defaults = operator.attrgetter(_func_defaults)
    +get_function_globals = operator.attrgetter(_func_globals)
    +
    +
    +if PY3:
    +
    +    def iterkeys(d, **kw):
    +        return iter(d.keys(**kw))
    +
    +    def itervalues(d, **kw):
    +        return iter(d.values(**kw))
    +
    +    def iteritems(d, **kw):
    +        return iter(d.items(**kw))
    +
    +    def iterlists(d, **kw):
    +        return iter(d.lists(**kw))
    +
    +    viewkeys = operator.methodcaller("keys")
    +
    +    viewvalues = operator.methodcaller("values")
    +
    +    viewitems = operator.methodcaller("items")
    +else:
    +
    +    def iterkeys(d, **kw):
    +        return d.iterkeys(**kw)
    +
    +    def itervalues(d, **kw):
    +        return d.itervalues(**kw)
    +
    +    def iteritems(d, **kw):
    +        return d.iteritems(**kw)
    +
    +    def iterlists(d, **kw):
    +        return d.iterlists(**kw)
    +
    +    viewkeys = operator.methodcaller("viewkeys")
    +
    +    viewvalues = operator.methodcaller("viewvalues")
    +
    +    viewitems = operator.methodcaller("viewitems")
    +
    +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
    +_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
    +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.")
    +_add_doc(
    +    iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary."
    +)
    +
    +
    +if PY3:
    +
    +    def b(s):
    +        return s.encode("latin-1")
    +
    +    def u(s):
    +        return s
    +
    +    unichr = chr
    +    import struct
    +
    +    int2byte = struct.Struct(">B").pack
    +    del struct
    +    byte2int = operator.itemgetter(0)
    +    indexbytes = operator.getitem
    +    iterbytes = iter
    +    import io
    +
    +    StringIO = io.StringIO
    +    BytesIO = io.BytesIO
    +    del io
    +    _assertCountEqual = "assertCountEqual"
    +    if sys.version_info[1] <= 1:
    +        _assertRaisesRegex = "assertRaisesRegexp"
    +        _assertRegex = "assertRegexpMatches"
    +    else:
    +        _assertRaisesRegex = "assertRaisesRegex"
    +        _assertRegex = "assertRegex"
    +else:
    +
    +    def b(s):
    +        return s
    +
    +    # Workaround for standalone backslash
    +
    +    def u(s):
    +        return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape")
    +
    +    unichr = unichr
    +    int2byte = chr
    +
    +    def byte2int(bs):
    +        return ord(bs[0])
    +
    +    def indexbytes(buf, i):
    +        return ord(buf[i])
    +
    +    iterbytes = functools.partial(itertools.imap, ord)
    +    import StringIO
    +
    +    StringIO = BytesIO = StringIO.StringIO
    +    _assertCountEqual = "assertItemsEqual"
    +    _assertRaisesRegex = "assertRaisesRegexp"
    +    _assertRegex = "assertRegexpMatches"
    +_add_doc(b, """Byte literal""")
    +_add_doc(u, """Text literal""")
    +
    +
    +def assertCountEqual(self, *args, **kwargs):
    +    return getattr(self, _assertCountEqual)(*args, **kwargs)
    +
    +
    +def assertRaisesRegex(self, *args, **kwargs):
    +    return getattr(self, _assertRaisesRegex)(*args, **kwargs)
    +
    +
    +def assertRegex(self, *args, **kwargs):
    +    return getattr(self, _assertRegex)(*args, **kwargs)
    +
    +
    +if PY3:
    +    exec_ = getattr(moves.builtins, "exec")
    +
    +    def reraise(tp, value, tb=None):
    +        try:
    +            if value is None:
    +                value = tp()
    +            if value.__traceback__ is not tb:
    +                raise value.with_traceback(tb)
    +            raise value
    +        finally:
    +            value = None
    +            tb = None
    +
    +
    +else:
    +
    +    def exec_(_code_, _globs_=None, _locs_=None):
    +        """Execute code in a namespace."""
    +        if _globs_ is None:
    +            frame = sys._getframe(1)
    +            _globs_ = frame.f_globals
    +            if _locs_ is None:
    +                _locs_ = frame.f_locals
    +            del frame
    +        elif _locs_ is None:
    +            _locs_ = _globs_
    +        exec("""exec _code_ in _globs_, _locs_""")
    +
    +    exec_(
    +        """def reraise(tp, value, tb=None):
    +    try:
    +        raise tp, value, tb
    +    finally:
    +        tb = None
    +"""
    +    )
    +
    +
    +if sys.version_info[:2] == (3, 2):
    +    exec_(
    +        """def raise_from(value, from_value):
    +    try:
    +        if from_value is None:
    +            raise value
    +        raise value from from_value
    +    finally:
    +        value = None
    +"""
    +    )
    +elif sys.version_info[:2] > (3, 2):
    +    exec_(
    +        """def raise_from(value, from_value):
    +    try:
    +        raise value from from_value
    +    finally:
    +        value = None
    +"""
    +    )
    +else:
    +
    +    def raise_from(value, from_value):
    +        raise value
    +
    +
    +print_ = getattr(moves.builtins, "print", None)
    +if print_ is None:
    +
    +    def print_(*args, **kwargs):
    +        """The new-style print function for Python 2.4 and 2.5."""
    +        fp = kwargs.pop("file", sys.stdout)
    +        if fp is None:
    +            return
    +
    +        def write(data):
    +            if not isinstance(data, basestring):
    +                data = str(data)
    +            # If the file has an encoding, encode unicode with it.
    +            if (
    +                isinstance(fp, file)
    +                and isinstance(data, unicode)
    +                and fp.encoding is not None
    +            ):
    +                errors = getattr(fp, "errors", None)
    +                if errors is None:
    +                    errors = "strict"
    +                data = data.encode(fp.encoding, errors)
    +            fp.write(data)
    +
    +        want_unicode = False
    +        sep = kwargs.pop("sep", None)
    +        if sep is not None:
    +            if isinstance(sep, unicode):
    +                want_unicode = True
    +            elif not isinstance(sep, str):
    +                raise TypeError("sep must be None or a string")
    +        end = kwargs.pop("end", None)
    +        if end is not None:
    +            if isinstance(end, unicode):
    +                want_unicode = True
    +            elif not isinstance(end, str):
    +                raise TypeError("end must be None or a string")
    +        if kwargs:
    +            raise TypeError("invalid keyword arguments to print()")
    +        if not want_unicode:
    +            for arg in args:
    +                if isinstance(arg, unicode):
    +                    want_unicode = True
    +                    break
    +        if want_unicode:
    +            newline = unicode("\n")
    +            space = unicode(" ")
    +        else:
    +            newline = "\n"
    +            space = " "
    +        if sep is None:
    +            sep = space
    +        if end is None:
    +            end = newline
    +        for i, arg in enumerate(args):
    +            if i:
    +                write(sep)
    +            write(arg)
    +        write(end)
    +
    +
    +if sys.version_info[:2] < (3, 3):
    +    _print = print_
    +
    +    def print_(*args, **kwargs):
    +        fp = kwargs.get("file", sys.stdout)
    +        flush = kwargs.pop("flush", False)
    +        _print(*args, **kwargs)
    +        if flush and fp is not None:
    +            fp.flush()
    +
    +
    +_add_doc(reraise, """Reraise an exception.""")
    +
    +if sys.version_info[0:2] < (3, 4):
    +
    +    def wraps(
    +        wrapped,
    +        assigned=functools.WRAPPER_ASSIGNMENTS,
    +        updated=functools.WRAPPER_UPDATES,
    +    ):
    +        def wrapper(f):
    +            f = functools.wraps(wrapped, assigned, updated)(f)
    +            f.__wrapped__ = wrapped
    +            return f
    +
    +        return wrapper
    +
    +
    +else:
    +    wraps = functools.wraps
    +
    +
    +def with_metaclass(meta, *bases):
    +    """Create a base class with a metaclass."""
    +    # This requires a bit of explanation: the basic idea is to make a dummy
    +    # metaclass for one level of class instantiation that replaces itself with
    +    # the actual metaclass.
    +    class metaclass(type):
    +        def __new__(cls, name, this_bases, d):
    +            return meta(name, bases, d)
    +
    +        @classmethod
    +        def __prepare__(cls, name, this_bases):
    +            return meta.__prepare__(name, bases)
    +
    +    return type.__new__(metaclass, "temporary_class", (), {})
    +
    +
    +def add_metaclass(metaclass):
    +    """Class decorator for creating a class with a metaclass."""
    +
    +    def wrapper(cls):
    +        orig_vars = cls.__dict__.copy()
    +        slots = orig_vars.get("__slots__")
    +        if slots is not None:
    +            if isinstance(slots, str):
    +                slots = [slots]
    +            for slots_var in slots:
    +                orig_vars.pop(slots_var)
    +        orig_vars.pop("__dict__", None)
    +        orig_vars.pop("__weakref__", None)
    +        if hasattr(cls, "__qualname__"):
    +            orig_vars["__qualname__"] = cls.__qualname__
    +        return metaclass(cls.__name__, cls.__bases__, orig_vars)
    +
    +    return wrapper
    +
    +
    +def ensure_binary(s, encoding="utf-8", errors="strict"):
    +    """Coerce **s** to six.binary_type.
    +
    +    For Python 2:
    +      - `unicode` -> encoded to `str`
    +      - `str` -> `str`
    +
    +    For Python 3:
    +      - `str` -> encoded to `bytes`
    +      - `bytes` -> `bytes`
    +    """
    +    if isinstance(s, text_type):
    +        return s.encode(encoding, errors)
    +    elif isinstance(s, binary_type):
    +        return s
    +    else:
    +        raise TypeError("not expecting type '%s'" % type(s))
    +
    +
    +def ensure_str(s, encoding="utf-8", errors="strict"):
    +    """Coerce *s* to `str`.
    +
    +    For Python 2:
    +      - `unicode` -> encoded to `str`
    +      - `str` -> `str`
    +
    +    For Python 3:
    +      - `str` -> `str`
    +      - `bytes` -> decoded to `str`
    +    """
    +    if not isinstance(s, (text_type, binary_type)):
    +        raise TypeError("not expecting type '%s'" % type(s))
    +    if PY2 and isinstance(s, text_type):
    +        s = s.encode(encoding, errors)
    +    elif PY3 and isinstance(s, binary_type):
    +        s = s.decode(encoding, errors)
    +    return s
    +
    +
    +def ensure_text(s, encoding="utf-8", errors="strict"):
    +    """Coerce *s* to six.text_type.
    +
    +    For Python 2:
    +      - `unicode` -> `unicode`
    +      - `str` -> `unicode`
    +
    +    For Python 3:
    +      - `str` -> `str`
    +      - `bytes` -> decoded to `str`
    +    """
    +    if isinstance(s, binary_type):
    +        return s.decode(encoding, errors)
    +    elif isinstance(s, text_type):
    +        return s
    +    else:
    +        raise TypeError("not expecting type '%s'" % type(s))
    +
    +
    +def python_2_unicode_compatible(klass):
    +    """
    +    A decorator that defines __unicode__ and __str__ methods under Python 2.
    +    Under Python 3 it does nothing.
    +
    +    To support Python 2 and 3 with a single code base, define a __str__ method
    +    returning text and apply this decorator to the class.
    +    """
    +    if PY2:
    +        if "__str__" not in klass.__dict__:
    +            raise ValueError(
    +                "@python_2_unicode_compatible cannot be applied "
    +                "to %s because it doesn't define __str__()." % klass.__name__
    +            )
    +        klass.__unicode__ = klass.__str__
    +        klass.__str__ = lambda self: self.__unicode__().encode("utf-8")
    +    return klass
    +
    +
    +# Complete the moves implementation.
    +# This code is at the end of this module to speed up module loading.
    +# Turn this module into a package.
    +__path__ = []  # required for PEP 302 and PEP 451
    +__package__ = __name__  # see PEP 366 @ReservedAssignment
    +if globals().get("__spec__") is not None:
    +    __spec__.submodule_search_locations = []  # PEP 451 @UndefinedVariable
    +# Remove other six meta path importers, since they cause problems. This can
    +# happen if six is removed from sys.modules and then reloaded. (Setuptools does
    +# this for some reason.)
    +if sys.meta_path:
    +    for i, importer in enumerate(sys.meta_path):
    +        # Here's some real nastiness: Another "instance" of the six module might
    +        # be floating around. Therefore, we can't use isinstance() to check for
    +        # the six meta path importer, since the other six instance will have
    +        # inserted an importer with different class.
    +        if (
    +            type(importer).__name__ == "_SixMetaPathImporter"
    +            and importer.name == __name__
    +        ):
    +            del sys.meta_path[i]
    +            break
    +    del i, importer
    +# Finally, add the importer to the meta path import hook.
    +sys.meta_path.append(_importer)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/__init__.py b/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/__init__.py
    new file mode 100644
    index 0000000..75b6bb1
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/__init__.py
    @@ -0,0 +1,19 @@
    +import sys
    +
    +try:
    +    # Our match_hostname function is the same as 3.5's, so we only want to
    +    # import the match_hostname function if it's at least that good.
    +    if sys.version_info < (3, 5):
    +        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 CertificateError, match_hostname
    +    except ImportError:
    +        # Our vendored copy
    +        from ._implementation import CertificateError, match_hostname
    +
    +# Not needed, but documenting what we provide.
    +__all__ = ("CertificateError", "match_hostname")
    diff --git a/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py b/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py
    new file mode 100644
    index 0000000..689208d
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py
    @@ -0,0 +1,160 @@
    +"""The match_hostname() function from Python 3.3.3, essential when using SSL."""
    +
    +# Note: This file is under the PSF license as the code comes from the python
    +# stdlib.   http://docs.python.org/3/license.html
    +
    +import re
    +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.
    +try:
    +    import ipaddress
    +except ImportError:
    +    ipaddress = None
    +
    +__version__ = "3.5.0.1"
    +
    +
    +class CertificateError(ValueError):
    +    pass
    +
    +
    +def _dnsname_match(dn, hostname, max_wildcards=1):
    +    """Matching according to RFC 6125, section 6.4.3
    +
    +    http://tools.ietf.org/html/rfc6125#section-6.4.3
    +    """
    +    pats = []
    +    if not dn:
    +        return False
    +
    +    # Ported from python3-syntax:
    +    # leftmost, *remainder = dn.split(r'.')
    +    parts = dn.split(r".")
    +    leftmost = parts[0]
    +    remainder = parts[1:]
    +
    +    wildcards = leftmost.count("*")
    +    if wildcards > max_wildcards:
    +        # Issue #17980: avoid denials of service by refusing more
    +        # than one wildcard per fragment.  A survey of established
    +        # policy among SSL implementations showed it to be a
    +        # reasonable choice.
    +        raise CertificateError(
    +            "too many wildcards in certificate DNS name: " + repr(dn)
    +        )
    +
    +    # speed up common case w/o wildcards
    +    if not wildcards:
    +        return dn.lower() == hostname.lower()
    +
    +    # RFC 6125, section 6.4.3, subitem 1.
    +    # The client SHOULD NOT attempt to match a presented identifier in which
    +    # the wildcard character comprises a label other than the left-most label.
    +    if leftmost == "*":
    +        # When '*' is a fragment by itself, it matches a non-empty dotless
    +        # fragment.
    +        pats.append("[^.]+")
    +    elif leftmost.startswith("xn--") or hostname.startswith("xn--"):
    +        # RFC 6125, section 6.4.3, subitem 3.
    +        # The client SHOULD NOT attempt to match a presented identifier
    +        # where the wildcard character is embedded within an A-label or
    +        # U-label of an internationalized domain name.
    +        pats.append(re.escape(leftmost))
    +    else:
    +        # Otherwise, '*' matches any dotless string, e.g. www*
    +        pats.append(re.escape(leftmost).replace(r"\*", "[^.]*"))
    +
    +    # add the remaining fragments, ignore any wildcards
    +    for frag in remainder:
    +        pats.append(re.escape(frag))
    +
    +    pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE)
    +    return pat.match(hostname)
    +
    +
    +def _to_unicode(obj):
    +    if isinstance(obj, str) and sys.version_info < (3,):
    +        obj = unicode(obj, encoding="ascii", errors="strict")
    +    return obj
    +
    +
    +def _ipaddress_match(ipname, host_ip):
    +    """Exact matching of IP addresses.
    +
    +    RFC 6125 explicitly doesn't define an algorithm for this
    +    (section 1.7.2 - "Out of Scope").
    +    """
    +    # OpenSSL may add a trailing newline to a subjectAltName's IP address
    +    # Divergence from upstream: ipaddress can't handle byte str
    +    ip = ipaddress.ip_address(_to_unicode(ipname).rstrip())
    +    return ip == host_ip
    +
    +
    +def match_hostname(cert, hostname):
    +    """Verify that *cert* (in decoded format as returned by
    +    SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 and RFC 6125
    +    rules are followed, but IP addresses are not accepted for *hostname*.
    +
    +    CertificateError is raised on failure. On success, the function
    +    returns nothing.
    +    """
    +    if not cert:
    +        raise ValueError(
    +            "empty or no certificate, match_hostname needs a "
    +            "SSL socket or SSL context with either "
    +            "CERT_OPTIONAL or CERT_REQUIRED"
    +        )
    +    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
    +        # byte strings.  addresses should be all ascii, so we consider it not
    +        # an ipaddress in this case
    +        host_ip = None
    +    except AttributeError:
    +        # Divergence from upstream: Make ipaddress library optional
    +        if ipaddress is None:
    +            host_ip = None
    +        else:
    +            raise
    +    dnsnames = []
    +    san = cert.get("subjectAltName", ())
    +    for key, value in san:
    +        if key == "DNS":
    +            if host_ip is None and _dnsname_match(value, hostname):
    +                return
    +            dnsnames.append(value)
    +        elif key == "IP Address":
    +            if host_ip is not None and _ipaddress_match(value, host_ip):
    +                return
    +            dnsnames.append(value)
    +    if not dnsnames:
    +        # The subject is only checked when there is no dNSName entry
    +        # in subjectAltName
    +        for sub in cert.get("subject", ()):
    +            for key, value in sub:
    +                # XXX according to RFC 2818, the most specific Common Name
    +                # must be used.
    +                if key == "commonName":
    +                    if _dnsname_match(value, hostname):
    +                        return
    +                    dnsnames.append(value)
    +    if len(dnsnames) > 1:
    +        raise CertificateError(
    +            "hostname %r "
    +            "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames)))
    +        )
    +    elif len(dnsnames) == 1:
    +        raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0]))
    +    else:
    +        raise CertificateError(
    +            "no appropriate commonName or subjectAltName fields were found"
    +        )
    diff --git a/venv/lib/python3.7/site-packages/urllib3/poolmanager.py b/venv/lib/python3.7/site-packages/urllib3/poolmanager.py
    new file mode 100644
    index 0000000..242a2f8
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/poolmanager.py
    @@ -0,0 +1,470 @@
    +from __future__ import absolute_import
    +import collections
    +import functools
    +import logging
    +
    +from ._collections import RecentlyUsedContainer
    +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
    +from .connectionpool import port_by_scheme
    +from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown
    +from .packages import six
    +from .packages.six.moves.urllib.parse import urljoin
    +from .request import RequestMethods
    +from .util.url import parse_url
    +from .util.retry import Retry
    +
    +
    +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"]
    +
    +
    +log = logging.getLogger(__name__)
    +
    +SSL_KEYWORDS = (
    +    "key_file",
    +    "cert_file",
    +    "cert_reqs",
    +    "ca_certs",
    +    "ssl_version",
    +    "ca_cert_dir",
    +    "ssl_context",
    +    "key_password",
    +)
    +
    +# All known keyword arguments that could be provided to the pool manager, its
    +# pools, or the underlying connections. This is used to construct a pool key.
    +_key_fields = (
    +    "key_scheme",  # str
    +    "key_host",  # str
    +    "key_port",  # int
    +    "key_timeout",  # int or float or Timeout
    +    "key_retries",  # int or Retry
    +    "key_strict",  # bool
    +    "key_block",  # bool
    +    "key_source_address",  # str
    +    "key_key_file",  # str
    +    "key_key_password",  # str
    +    "key_cert_file",  # str
    +    "key_cert_reqs",  # str
    +    "key_ca_certs",  # str
    +    "key_ssl_version",  # str
    +    "key_ca_cert_dir",  # str
    +    "key_ssl_context",  # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext
    +    "key_maxsize",  # int
    +    "key_headers",  # dict
    +    "key__proxy",  # parsed proxy url
    +    "key__proxy_headers",  # dict
    +    "key_socket_options",  # list of (level (int), optname (int), value (int or str)) tuples
    +    "key__socks_options",  # dict
    +    "key_assert_hostname",  # bool or string
    +    "key_assert_fingerprint",  # str
    +    "key_server_hostname",  # str
    +)
    +
    +#: The namedtuple class used to construct keys for the connection pool.
    +#: All custom key schemes should include the fields in this key at a minimum.
    +PoolKey = collections.namedtuple("PoolKey", _key_fields)
    +
    +
    +def _default_key_normalizer(key_class, request_context):
    +    """
    +    Create a pool key out of a request context dictionary.
    +
    +    According to RFC 3986, both the scheme and host are case-insensitive.
    +    Therefore, this function normalizes both before constructing the pool
    +    key for an HTTPS request. If you wish to change this behaviour, provide
    +    alternate callables to ``key_fn_by_scheme``.
    +
    +    :param key_class:
    +        The class to use when constructing the key. This should be a namedtuple
    +        with the ``scheme`` and ``host`` keys at a minimum.
    +    :type  key_class: namedtuple
    +    :param request_context:
    +        A dictionary-like object that contain the context for a request.
    +    :type  request_context: dict
    +
    +    :return: A namedtuple that can be used as a connection pool key.
    +    :rtype:  PoolKey
    +    """
    +    # Since we mutate the dictionary, make a copy first
    +    context = request_context.copy()
    +    context["scheme"] = context["scheme"].lower()
    +    context["host"] = context["host"].lower()
    +
    +    # These are both dictionaries and need to be transformed into frozensets
    +    for key in ("headers", "_proxy_headers", "_socks_options"):
    +        if key in context and context[key] is not None:
    +            context[key] = frozenset(context[key].items())
    +
    +    # The socket_options key may be a list and needs to be transformed into a
    +    # tuple.
    +    socket_opts = context.get("socket_options")
    +    if socket_opts is not None:
    +        context["socket_options"] = tuple(socket_opts)
    +
    +    # Map the kwargs to the names in the namedtuple - this is necessary since
    +    # namedtuples can't have fields starting with '_'.
    +    for key in list(context.keys()):
    +        context["key_" + key] = context.pop(key)
    +
    +    # Default to ``None`` for keys missing from the context
    +    for field in key_class._fields:
    +        if field not in context:
    +            context[field] = None
    +
    +    return key_class(**context)
    +
    +
    +#: A dictionary that maps a scheme to a callable that creates a pool key.
    +#: This can be used to alter the way pool keys are constructed, if desired.
    +#: Each PoolManager makes a copy of this dictionary so they can be configured
    +#: globally here, or individually on the instance.
    +key_fn_by_scheme = {
    +    "http": functools.partial(_default_key_normalizer, PoolKey),
    +    "https": functools.partial(_default_key_normalizer, PoolKey),
    +}
    +
    +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool}
    +
    +
    +class PoolManager(RequestMethods):
    +    """
    +    Allows for arbitrary requests while transparently keeping track of
    +    necessary connection pools for you.
    +
    +    :param num_pools:
    +        Number of connection pools to cache before discarding the least
    +        recently used pool.
    +
    +    :param headers:
    +        Headers to include with all requests, unless other headers are given
    +        explicitly.
    +
    +    :param \\**connection_pool_kw:
    +        Additional parameters are used to create fresh
    +        :class:`urllib3.connectionpool.ConnectionPool` instances.
    +
    +    Example::
    +
    +        >>> manager = PoolManager(num_pools=2)
    +        >>> r = manager.request('GET', 'http://google.com/')
    +        >>> r = manager.request('GET', 'http://google.com/mail')
    +        >>> r = manager.request('GET', 'http://yahoo.com/')
    +        >>> len(manager.pools)
    +        2
    +
    +    """
    +
    +    proxy = None
    +
    +    def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
    +        RequestMethods.__init__(self, headers)
    +        self.connection_pool_kw = connection_pool_kw
    +        self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close())
    +
    +        # Locally set the pool classes and keys so other PoolManagers can
    +        # override them.
    +        self.pool_classes_by_scheme = pool_classes_by_scheme
    +        self.key_fn_by_scheme = key_fn_by_scheme.copy()
    +
    +    def __enter__(self):
    +        return self
    +
    +    def __exit__(self, exc_type, exc_val, exc_tb):
    +        self.clear()
    +        # Return False to re-raise any potential exceptions
    +        return False
    +
    +    def _new_pool(self, scheme, host, port, request_context=None):
    +        """
    +        Create a new :class:`ConnectionPool` based on host, port, scheme, and
    +        any additional pool keyword arguments.
    +
    +        If ``request_context`` is provided, it is provided as keyword arguments
    +        to the pool class used. This method is used to actually create the
    +        connection pools handed out by :meth:`connection_from_url` and
    +        companion methods. It is intended to be overridden for customization.
    +        """
    +        pool_cls = self.pool_classes_by_scheme[scheme]
    +        if request_context is None:
    +            request_context = self.connection_pool_kw.copy()
    +
    +        # Although the context has everything necessary to create the pool,
    +        # this function has historically only used the scheme, host, and port
    +        # in the positional args. When an API change is acceptable these can
    +        # be removed.
    +        for key in ("scheme", "host", "port"):
    +            request_context.pop(key, None)
    +
    +        if scheme == "http":
    +            for kw in SSL_KEYWORDS:
    +                request_context.pop(kw, None)
    +
    +        return pool_cls(host, port, **request_context)
    +
    +    def clear(self):
    +        """
    +        Empty our store of pools and direct them all to close.
    +
    +        This will not affect in-flight connections, but they will not be
    +        re-used after completion.
    +        """
    +        self.pools.clear()
    +
    +    def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
    +        """
    +        Get a :class:`ConnectionPool` based on the host, port, and scheme.
    +
    +        If ``port`` isn't given, it will be derived from the ``scheme`` using
    +        ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is
    +        provided, it is merged with the instance's ``connection_pool_kw``
    +        variable and used to create the new connection pool, if one is
    +        needed.
    +        """
    +
    +        if not host:
    +            raise LocationValueError("No host specified.")
    +
    +        request_context = self._merge_pool_kwargs(pool_kwargs)
    +        request_context["scheme"] = scheme or "http"
    +        if not port:
    +            port = port_by_scheme.get(request_context["scheme"].lower(), 80)
    +        request_context["port"] = port
    +        request_context["host"] = host
    +
    +        return self.connection_from_context(request_context)
    +
    +    def connection_from_context(self, request_context):
    +        """
    +        Get a :class:`ConnectionPool` based on the request context.
    +
    +        ``request_context`` must at least contain the ``scheme`` key and its
    +        value must be a key in ``key_fn_by_scheme`` instance variable.
    +        """
    +        scheme = request_context["scheme"].lower()
    +        pool_key_constructor = self.key_fn_by_scheme[scheme]
    +        pool_key = pool_key_constructor(request_context)
    +
    +        return self.connection_from_pool_key(pool_key, request_context=request_context)
    +
    +    def connection_from_pool_key(self, pool_key, request_context=None):
    +        """
    +        Get a :class:`ConnectionPool` based on the provided pool key.
    +
    +        ``pool_key`` should be a namedtuple that only contains immutable
    +        objects. At a minimum it must have the ``scheme``, ``host``, and
    +        ``port`` fields.
    +        """
    +        with self.pools.lock:
    +            # If the scheme, host, or port doesn't match existing open
    +            # connections, open a new ConnectionPool.
    +            pool = self.pools.get(pool_key)
    +            if pool:
    +                return pool
    +
    +            # Make a fresh ConnectionPool of the desired type
    +            scheme = request_context["scheme"]
    +            host = request_context["host"]
    +            port = request_context["port"]
    +            pool = self._new_pool(scheme, host, port, request_context=request_context)
    +            self.pools[pool_key] = pool
    +
    +        return pool
    +
    +    def connection_from_url(self, url, pool_kwargs=None):
    +        """
    +        Similar to :func:`urllib3.connectionpool.connection_from_url`.
    +
    +        If ``pool_kwargs`` is not provided and a new pool needs to be
    +        constructed, ``self.connection_pool_kw`` is used to initialize
    +        the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs``
    +        is provided, it is used instead. Note that if a new pool does not
    +        need to be created for the request, the provided ``pool_kwargs`` are
    +        not used.
    +        """
    +        u = parse_url(url)
    +        return self.connection_from_host(
    +            u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs
    +        )
    +
    +    def _merge_pool_kwargs(self, override):
    +        """
    +        Merge a dictionary of override values for self.connection_pool_kw.
    +
    +        This does not modify self.connection_pool_kw and returns a new dict.
    +        Any keys in the override dictionary with a value of ``None`` are
    +        removed from the merged dictionary.
    +        """
    +        base_pool_kwargs = self.connection_pool_kw.copy()
    +        if override:
    +            for key, value in override.items():
    +                if value is None:
    +                    try:
    +                        del base_pool_kwargs[key]
    +                    except KeyError:
    +                        pass
    +                else:
    +                    base_pool_kwargs[key] = value
    +        return base_pool_kwargs
    +
    +    def urlopen(self, method, url, redirect=True, **kw):
    +        """
    +        Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen`
    +        with custom cross-host redirect logic and only sends the request-uri
    +        portion of the ``url``.
    +
    +        The given ``url`` parameter must be absolute, such that an appropriate
    +        :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
    +        """
    +        u = parse_url(url)
    +        conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)
    +
    +        kw["assert_same_host"] = False
    +        kw["redirect"] = False
    +
    +        if "headers" not in kw:
    +            kw["headers"] = self.headers.copy()
    +
    +        if self.proxy is not None and u.scheme == "http":
    +            response = conn.urlopen(method, url, **kw)
    +        else:
    +            response = conn.urlopen(method, u.request_uri, **kw)
    +
    +        redirect_location = redirect and response.get_redirect_location()
    +        if not redirect_location:
    +            return response
    +
    +        # Support relative URLs for redirecting.
    +        redirect_location = urljoin(url, redirect_location)
    +
    +        # RFC 7231, Section 6.4.4
    +        if response.status == 303:
    +            method = "GET"
    +
    +        retries = kw.get("retries")
    +        if not isinstance(retries, Retry):
    +            retries = Retry.from_int(retries, redirect=redirect)
    +
    +        # Strip headers marked as unsafe to forward to the redirected location.
    +        # Check remove_headers_on_redirect to avoid a potential network call within
    +        # conn.is_same_host() which may use socket.gethostbyname() in the future.
    +        if retries.remove_headers_on_redirect and not conn.is_same_host(
    +            redirect_location
    +        ):
    +            headers = list(six.iterkeys(kw["headers"]))
    +            for header in headers:
    +                if header.lower() in retries.remove_headers_on_redirect:
    +                    kw["headers"].pop(header, None)
    +
    +        try:
    +            retries = retries.increment(method, url, response=response, _pool=conn)
    +        except MaxRetryError:
    +            if retries.raise_on_redirect:
    +                raise
    +            return response
    +
    +        kw["retries"] = retries
    +        kw["redirect"] = redirect
    +
    +        log.info("Redirecting %s -> %s", url, redirect_location)
    +        return self.urlopen(method, redirect_location, **kw)
    +
    +
    +class ProxyManager(PoolManager):
    +    """
    +    Behaves just like :class:`PoolManager`, but sends all requests through
    +    the defined proxy, using the CONNECT method for HTTPS URLs.
    +
    +    :param proxy_url:
    +        The URL of the proxy to be used.
    +
    +    :param proxy_headers:
    +        A dictionary containing headers that will be sent to the proxy. In case
    +        of HTTP they are being sent with each request, while in the
    +        HTTPS/CONNECT case they are sent only once. Could be used for proxy
    +        authentication.
    +
    +    Example:
    +        >>> proxy = urllib3.ProxyManager('http://localhost:3128/')
    +        >>> r1 = proxy.request('GET', 'http://google.com/')
    +        >>> r2 = proxy.request('GET', 'http://httpbin.org/')
    +        >>> len(proxy.pools)
    +        1
    +        >>> r3 = proxy.request('GET', 'https://httpbin.org/')
    +        >>> r4 = proxy.request('GET', 'https://twitter.com/')
    +        >>> len(proxy.pools)
    +        3
    +
    +    """
    +
    +    def __init__(
    +        self,
    +        proxy_url,
    +        num_pools=10,
    +        headers=None,
    +        proxy_headers=None,
    +        **connection_pool_kw
    +    ):
    +
    +        if isinstance(proxy_url, HTTPConnectionPool):
    +            proxy_url = "%s://%s:%i" % (
    +                proxy_url.scheme,
    +                proxy_url.host,
    +                proxy_url.port,
    +            )
    +        proxy = parse_url(proxy_url)
    +        if not proxy.port:
    +            port = port_by_scheme.get(proxy.scheme, 80)
    +            proxy = proxy._replace(port=port)
    +
    +        if proxy.scheme not in ("http", "https"):
    +            raise ProxySchemeUnknown(proxy.scheme)
    +
    +        self.proxy = proxy
    +        self.proxy_headers = proxy_headers or {}
    +
    +        connection_pool_kw["_proxy"] = self.proxy
    +        connection_pool_kw["_proxy_headers"] = self.proxy_headers
    +
    +        super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw)
    +
    +    def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
    +        if scheme == "https":
    +            return super(ProxyManager, self).connection_from_host(
    +                host, port, scheme, pool_kwargs=pool_kwargs
    +            )
    +
    +        return super(ProxyManager, self).connection_from_host(
    +            self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs
    +        )
    +
    +    def _set_proxy_headers(self, url, headers=None):
    +        """
    +        Sets headers needed by proxies: specifically, the Accept and Host
    +        headers. Only sets headers not provided by the user.
    +        """
    +        headers_ = {"Accept": "*/*"}
    +
    +        netloc = parse_url(url).netloc
    +        if netloc:
    +            headers_["Host"] = netloc
    +
    +        if headers:
    +            headers_.update(headers)
    +        return headers_
    +
    +    def urlopen(self, method, url, redirect=True, **kw):
    +        "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
    +        u = parse_url(url)
    +
    +        if u.scheme == "http":
    +            # For proxied HTTPS requests, httplib sets the necessary headers
    +            # on the CONNECT to the proxy. For HTTP, we'll definitely
    +            # need to set 'Host' at the very least.
    +            headers = kw.get("headers", self.headers)
    +            kw["headers"] = self._set_proxy_headers(url, headers)
    +
    +        return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw)
    +
    +
    +def proxy_from_url(url, **kw):
    +    return ProxyManager(proxy_url=url, **kw)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/request.py b/venv/lib/python3.7/site-packages/urllib3/request.py
    new file mode 100644
    index 0000000..55f160b
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/request.py
    @@ -0,0 +1,171 @@
    +from __future__ import absolute_import
    +
    +from .filepost import encode_multipart_formdata
    +from .packages.six.moves.urllib.parse import urlencode
    +
    +
    +__all__ = ["RequestMethods"]
    +
    +
    +class RequestMethods(object):
    +    """
    +    Convenience mixin for classes who implement a :meth:`urlopen` method, such
    +    as :class:`~urllib3.connectionpool.HTTPConnectionPool` and
    +    :class:`~urllib3.poolmanager.PoolManager`.
    +
    +    Provides behavior for making common types of HTTP request methods and
    +    decides which type of request field encoding to use.
    +
    +    Specifically,
    +
    +    :meth:`.request_encode_url` is for sending requests whose fields are
    +    encoded in the URL (such as GET, HEAD, DELETE).
    +
    +    :meth:`.request_encode_body` is for sending requests whose fields are
    +    encoded in the *body* of the request using multipart or www-form-urlencoded
    +    (such as for POST, PUT, PATCH).
    +
    +    :meth:`.request` is for making any kind of request, it will look up the
    +    appropriate encoding format and use one of the above two methods to make
    +    the request.
    +
    +    Initializer parameters:
    +
    +    :param headers:
    +        Headers to include with all requests, unless other headers are given
    +        explicitly.
    +    """
    +
    +    _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"}
    +
    +    def __init__(self, headers=None):
    +        self.headers = headers or {}
    +
    +    def urlopen(
    +        self,
    +        method,
    +        url,
    +        body=None,
    +        headers=None,
    +        encode_multipart=True,
    +        multipart_boundary=None,
    +        **kw
    +    ):  # Abstract
    +        raise NotImplementedError(
    +            "Classes extending RequestMethods must implement "
    +            "their own ``urlopen`` method."
    +        )
    +
    +    def request(self, method, url, fields=None, headers=None, **urlopen_kw):
    +        """
    +        Make a request using :meth:`urlopen` with the appropriate encoding of
    +        ``fields`` based on the ``method`` used.
    +
    +        This is a convenience method that requires the least amount of manual
    +        effort. It can be used in most situations, while still having the
    +        option to drop down to more specific methods when necessary, such as
    +        :meth:`request_encode_url`, :meth:`request_encode_body`,
    +        or even the lowest level :meth:`urlopen`.
    +        """
    +        method = method.upper()
    +
    +        urlopen_kw["request_url"] = url
    +
    +        if method in self._encode_url_methods:
    +            return self.request_encode_url(
    +                method, url, fields=fields, headers=headers, **urlopen_kw
    +            )
    +        else:
    +            return self.request_encode_body(
    +                method, url, fields=fields, headers=headers, **urlopen_kw
    +            )
    +
    +    def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw):
    +        """
    +        Make a request using :meth:`urlopen` with the ``fields`` encoded in
    +        the url. This is useful for request methods like GET, HEAD, DELETE, etc.
    +        """
    +        if headers is None:
    +            headers = self.headers
    +
    +        extra_kw = {"headers": headers}
    +        extra_kw.update(urlopen_kw)
    +
    +        if fields:
    +            url += "?" + urlencode(fields)
    +
    +        return self.urlopen(method, url, **extra_kw)
    +
    +    def request_encode_body(
    +        self,
    +        method,
    +        url,
    +        fields=None,
    +        headers=None,
    +        encode_multipart=True,
    +        multipart_boundary=None,
    +        **urlopen_kw
    +    ):
    +        """
    +        Make a request using :meth:`urlopen` with the ``fields`` encoded in
    +        the body. This is useful for request methods like POST, PUT, PATCH, etc.
    +
    +        When ``encode_multipart=True`` (default), then
    +        :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode
    +        the payload with the appropriate content type. Otherwise
    +        :meth:`urllib.urlencode` is used with the
    +        'application/x-www-form-urlencoded' content type.
    +
    +        Multipart encoding must be used when posting files, and it's reasonably
    +        safe to use it in other times too. However, it may break request
    +        signing, such as with OAuth.
    +
    +        Supports an optional ``fields`` parameter of key/value strings AND
    +        key/filetuple. A filetuple is a (filename, data, MIME type) tuple where
    +        the MIME type is optional. For example::
    +
    +            fields = {
    +                'foo': 'bar',
    +                'fakefile': ('foofile.txt', 'contents of foofile'),
    +                'realfile': ('barfile.txt', open('realfile').read()),
    +                'typedfile': ('bazfile.bin', open('bazfile').read(),
    +                              'image/jpeg'),
    +                'nonamefile': 'contents of nonamefile field',
    +            }
    +
    +        When uploading a file, providing a filename (the first parameter of the
    +        tuple) is optional but recommended to best mimic behavior of browsers.
    +
    +        Note that if ``headers`` are supplied, the 'Content-Type' header will
    +        be overwritten because it depends on the dynamic random boundary string
    +        which is used to compose the body of the request. The random boundary
    +        string can be explicitly set with the ``multipart_boundary`` parameter.
    +        """
    +        if headers is None:
    +            headers = self.headers
    +
    +        extra_kw = {"headers": {}}
    +
    +        if fields:
    +            if "body" in urlopen_kw:
    +                raise TypeError(
    +                    "request got values for both 'fields' and 'body', can only specify one."
    +                )
    +
    +            if encode_multipart:
    +                body, content_type = encode_multipart_formdata(
    +                    fields, boundary=multipart_boundary
    +                )
    +            else:
    +                body, content_type = (
    +                    urlencode(fields),
    +                    "application/x-www-form-urlencoded",
    +                )
    +
    +            extra_kw["body"] = body
    +            extra_kw["headers"] = {"Content-Type": content_type}
    +
    +        extra_kw["headers"].update(headers)
    +        extra_kw.update(urlopen_kw)
    +
    +        return self.urlopen(method, url, **extra_kw)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/response.py b/venv/lib/python3.7/site-packages/urllib3/response.py
    new file mode 100644
    index 0000000..adc321e
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/response.py
    @@ -0,0 +1,809 @@
    +from __future__ import absolute_import
    +from contextlib import contextmanager
    +import zlib
    +import io
    +import logging
    +from socket import timeout as SocketTimeout
    +from socket import error as SocketError
    +
    +try:
    +    import brotli
    +except ImportError:
    +    brotli = None
    +
    +from ._collections import HTTPHeaderDict
    +from .exceptions import (
    +    BodyNotHttplibCompatible,
    +    ProtocolError,
    +    DecodeError,
    +    ReadTimeoutError,
    +    ResponseNotChunked,
    +    IncompleteRead,
    +    InvalidHeader,
    +)
    +from .packages.six import string_types as basestring, PY3
    +from .packages.six.moves import http_client as httplib
    +from .connection import HTTPException, BaseSSLError
    +from .util.response import is_fp_closed, is_response_to_head
    +
    +log = logging.getLogger(__name__)
    +
    +
    +class DeflateDecoder(object):
    +    def __init__(self):
    +        self._first_try = True
    +        self._data = b""
    +        self._obj = zlib.decompressobj()
    +
    +    def __getattr__(self, name):
    +        return getattr(self._obj, name)
    +
    +    def decompress(self, data):
    +        if not data:
    +            return data
    +
    +        if not self._first_try:
    +            return self._obj.decompress(data)
    +
    +        self._data += data
    +        try:
    +            decompressed = self._obj.decompress(data)
    +            if decompressed:
    +                self._first_try = False
    +                self._data = None
    +            return decompressed
    +        except zlib.error:
    +            self._first_try = False
    +            self._obj = zlib.decompressobj(-zlib.MAX_WBITS)
    +            try:
    +                return self.decompress(self._data)
    +            finally:
    +                self._data = None
    +
    +
    +class GzipDecoderState(object):
    +
    +    FIRST_MEMBER = 0
    +    OTHER_MEMBERS = 1
    +    SWALLOW_DATA = 2
    +
    +
    +class GzipDecoder(object):
    +    def __init__(self):
    +        self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS)
    +        self._state = GzipDecoderState.FIRST_MEMBER
    +
    +    def __getattr__(self, name):
    +        return getattr(self._obj, name)
    +
    +    def decompress(self, data):
    +        ret = bytearray()
    +        if self._state == GzipDecoderState.SWALLOW_DATA or not data:
    +            return bytes(ret)
    +        while True:
    +            try:
    +                ret += self._obj.decompress(data)
    +            except zlib.error:
    +                previous_state = self._state
    +                # Ignore data after the first error
    +                self._state = GzipDecoderState.SWALLOW_DATA
    +                if previous_state == GzipDecoderState.OTHER_MEMBERS:
    +                    # Allow trailing garbage acceptable in other gzip clients
    +                    return bytes(ret)
    +                raise
    +            data = self._obj.unused_data
    +            if not data:
    +                return bytes(ret)
    +            self._state = GzipDecoderState.OTHER_MEMBERS
    +            self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS)
    +
    +
    +if brotli is not None:
    +
    +    class BrotliDecoder(object):
    +        # Supports both 'brotlipy' and 'Brotli' packages
    +        # since they share an import name. The top branches
    +        # are for 'brotlipy' and bottom branches for 'Brotli'
    +        def __init__(self):
    +            self._obj = brotli.Decompressor()
    +
    +        def decompress(self, data):
    +            if hasattr(self._obj, "decompress"):
    +                return self._obj.decompress(data)
    +            return self._obj.process(data)
    +
    +        def flush(self):
    +            if hasattr(self._obj, "flush"):
    +                return self._obj.flush()
    +            return b""
    +
    +
    +class MultiDecoder(object):
    +    """
    +    From RFC7231:
    +        If one or more encodings have been applied to a representation, the
    +        sender that applied the encodings MUST generate a Content-Encoding
    +        header field that lists the content codings in the order in which
    +        they were applied.
    +    """
    +
    +    def __init__(self, modes):
    +        self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")]
    +
    +    def flush(self):
    +        return self._decoders[0].flush()
    +
    +    def decompress(self, data):
    +        for d in reversed(self._decoders):
    +            data = d.decompress(data)
    +        return data
    +
    +
    +def _get_decoder(mode):
    +    if "," in mode:
    +        return MultiDecoder(mode)
    +
    +    if mode == "gzip":
    +        return GzipDecoder()
    +
    +    if brotli is not None and mode == "br":
    +        return BrotliDecoder()
    +
    +    return DeflateDecoder()
    +
    +
    +class HTTPResponse(io.IOBase):
    +    """
    +    HTTP Response container.
    +
    +    Backwards-compatible to httplib's HTTPResponse but the response ``body`` is
    +    loaded and decoded on-demand when the ``data`` property is accessed.  This
    +    class is also compatible with the Python standard library's :mod:`io`
    +    module, and can hence be treated as a readable object in the context of that
    +    framework.
    +
    +    Extra parameters for behaviour not present in httplib.HTTPResponse:
    +
    +    :param preload_content:
    +        If True, the response's body will be preloaded during construction.
    +
    +    :param decode_content:
    +        If True, will attempt to decode the body based on the
    +        'content-encoding' header.
    +
    +    :param original_response:
    +        When this HTTPResponse wrapper is generated from an httplib.HTTPResponse
    +        object, it's convenient to include the original for debug purposes. It's
    +        otherwise unused.
    +
    +    :param retries:
    +        The retries contains the last :class:`~urllib3.util.retry.Retry` that
    +        was used during the request.
    +
    +    :param enforce_content_length:
    +        Enforce content length checking. Body returned by server must match
    +        value of Content-Length header, if present. Otherwise, raise error.
    +    """
    +
    +    CONTENT_DECODERS = ["gzip", "deflate"]
    +    if brotli is not None:
    +        CONTENT_DECODERS += ["br"]
    +    REDIRECT_STATUSES = [301, 302, 303, 307, 308]
    +
    +    def __init__(
    +        self,
    +        body="",
    +        headers=None,
    +        status=0,
    +        version=0,
    +        reason=None,
    +        strict=0,
    +        preload_content=True,
    +        decode_content=True,
    +        original_response=None,
    +        pool=None,
    +        connection=None,
    +        msg=None,
    +        retries=None,
    +        enforce_content_length=False,
    +        request_method=None,
    +        request_url=None,
    +        auto_close=True,
    +    ):
    +
    +        if isinstance(headers, HTTPHeaderDict):
    +            self.headers = headers
    +        else:
    +            self.headers = HTTPHeaderDict(headers)
    +        self.status = status
    +        self.version = version
    +        self.reason = reason
    +        self.strict = strict
    +        self.decode_content = decode_content
    +        self.retries = retries
    +        self.enforce_content_length = enforce_content_length
    +        self.auto_close = auto_close
    +
    +        self._decoder = None
    +        self._body = None
    +        self._fp = None
    +        self._original_response = original_response
    +        self._fp_bytes_read = 0
    +        self.msg = msg
    +        self._request_url = request_url
    +
    +        if body and isinstance(body, (basestring, bytes)):
    +            self._body = body
    +
    +        self._pool = pool
    +        self._connection = connection
    +
    +        if hasattr(body, "read"):
    +            self._fp = body
    +
    +        # Are we using the chunked-style of transfer encoding?
    +        self.chunked = False
    +        self.chunk_left = None
    +        tr_enc = self.headers.get("transfer-encoding", "").lower()
    +        # Don't incur the penalty of creating a list and then discarding it
    +        encodings = (enc.strip() for enc in tr_enc.split(","))
    +        if "chunked" in encodings:
    +            self.chunked = True
    +
    +        # Determine length of response
    +        self.length_remaining = self._init_length(request_method)
    +
    +        # If requested, preload the body.
    +        if preload_content and not self._body:
    +            self._body = self.read(decode_content=decode_content)
    +
    +    def get_redirect_location(self):
    +        """
    +        Should we redirect and where to?
    +
    +        :returns: Truthy redirect location string if we got a redirect status
    +            code and valid location. ``None`` if redirect status and no
    +            location. ``False`` if not a redirect status code.
    +        """
    +        if self.status in self.REDIRECT_STATUSES:
    +            return self.headers.get("location")
    +
    +        return False
    +
    +    def release_conn(self):
    +        if not self._pool or not self._connection:
    +            return
    +
    +        self._pool._put_conn(self._connection)
    +        self._connection = None
    +
    +    @property
    +    def data(self):
    +        # For backwords-compat with earlier urllib3 0.4 and earlier.
    +        if self._body:
    +            return self._body
    +
    +        if self._fp:
    +            return self.read(cache_content=True)
    +
    +    @property
    +    def connection(self):
    +        return self._connection
    +
    +    def isclosed(self):
    +        return is_fp_closed(self._fp)
    +
    +    def tell(self):
    +        """
    +        Obtain the number of bytes pulled over the wire so far. May differ from
    +        the amount of content returned by :meth:``HTTPResponse.read`` if bytes
    +        are encoded on the wire (e.g, compressed).
    +        """
    +        return self._fp_bytes_read
    +
    +    def _init_length(self, request_method):
    +        """
    +        Set initial length value for Response content if available.
    +        """
    +        length = self.headers.get("content-length")
    +
    +        if length is not None:
    +            if self.chunked:
    +                # This Response will fail with an IncompleteRead if it can't be
    +                # received as chunked. This method falls back to attempt reading
    +                # the response before raising an exception.
    +                log.warning(
    +                    "Received response with both Content-Length and "
    +                    "Transfer-Encoding set. This is expressly forbidden "
    +                    "by RFC 7230 sec 3.3.2. Ignoring Content-Length and "
    +                    "attempting to process response as Transfer-Encoding: "
    +                    "chunked."
    +                )
    +                return None
    +
    +            try:
    +                # RFC 7230 section 3.3.2 specifies multiple content lengths can
    +                # be sent in a single Content-Length header
    +                # (e.g. Content-Length: 42, 42). This line ensures the values
    +                # are all valid ints and that as long as the `set` length is 1,
    +                # all values are the same. Otherwise, the header is invalid.
    +                lengths = set([int(val) for val in length.split(",")])
    +                if len(lengths) > 1:
    +                    raise InvalidHeader(
    +                        "Content-Length contained multiple "
    +                        "unmatching values (%s)" % length
    +                    )
    +                length = lengths.pop()
    +            except ValueError:
    +                length = None
    +            else:
    +                if length < 0:
    +                    length = None
    +
    +        # Convert status to int for comparison
    +        # In some cases, httplib returns a status of "_UNKNOWN"
    +        try:
    +            status = int(self.status)
    +        except ValueError:
    +            status = 0
    +
    +        # Check for responses that shouldn't include a body
    +        if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD":
    +            length = 0
    +
    +        return length
    +
    +    def _init_decoder(self):
    +        """
    +        Set-up the _decoder attribute if necessary.
    +        """
    +        # Note: content-encoding value should be case-insensitive, per RFC 7230
    +        # Section 3.2
    +        content_encoding = self.headers.get("content-encoding", "").lower()
    +        if self._decoder is None:
    +            if content_encoding in self.CONTENT_DECODERS:
    +                self._decoder = _get_decoder(content_encoding)
    +            elif "," in content_encoding:
    +                encodings = [
    +                    e.strip()
    +                    for e in content_encoding.split(",")
    +                    if e.strip() in self.CONTENT_DECODERS
    +                ]
    +                if len(encodings):
    +                    self._decoder = _get_decoder(content_encoding)
    +
    +    DECODER_ERROR_CLASSES = (IOError, zlib.error)
    +    if brotli is not None:
    +        DECODER_ERROR_CLASSES += (brotli.error,)
    +
    +    def _decode(self, data, decode_content, flush_decoder):
    +        """
    +        Decode the data passed in and potentially flush the decoder.
    +        """
    +        if not decode_content:
    +            return data
    +
    +        try:
    +            if self._decoder:
    +                data = self._decoder.decompress(data)
    +        except self.DECODER_ERROR_CLASSES as e:
    +            content_encoding = self.headers.get("content-encoding", "").lower()
    +            raise DecodeError(
    +                "Received response with content-encoding: %s, but "
    +                "failed to decode it." % content_encoding,
    +                e,
    +            )
    +        if flush_decoder:
    +            data += self._flush_decoder()
    +
    +        return data
    +
    +    def _flush_decoder(self):
    +        """
    +        Flushes the decoder. Should only be called if the decoder is actually
    +        being used.
    +        """
    +        if self._decoder:
    +            buf = self._decoder.decompress(b"")
    +            return buf + self._decoder.flush()
    +
    +        return b""
    +
    +    @contextmanager
    +    def _error_catcher(self):
    +        """
    +        Catch low-level python exceptions, instead re-raising urllib3
    +        variants, so that low-level exceptions are not leaked in the
    +        high-level api.
    +
    +        On exit, release the connection back to the pool.
    +        """
    +        clean_exit = False
    +
    +        try:
    +            try:
    +                yield
    +
    +            except SocketTimeout:
    +                # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
    +                # there is yet no clean way to get at it from this context.
    +                raise ReadTimeoutError(self._pool, None, "Read timed out.")
    +
    +            except BaseSSLError as e:
    +                # FIXME: Is there a better way to differentiate between SSLErrors?
    +                if "read operation timed out" not in str(e):  # Defensive:
    +                    # This shouldn't happen but just in case we're missing an edge
    +                    # case, let's avoid swallowing SSL errors.
    +                    raise
    +
    +                raise ReadTimeoutError(self._pool, None, "Read timed out.")
    +
    +            except (HTTPException, SocketError) as e:
    +                # This includes IncompleteRead.
    +                raise ProtocolError("Connection broken: %r" % e, e)
    +
    +            # If no exception is thrown, we should avoid cleaning up
    +            # unnecessarily.
    +            clean_exit = True
    +        finally:
    +            # If we didn't terminate cleanly, we need to throw away our
    +            # connection.
    +            if not clean_exit:
    +                # The response may not be closed but we're not going to use it
    +                # anymore so close it now to ensure that the connection is
    +                # released back to the pool.
    +                if self._original_response:
    +                    self._original_response.close()
    +
    +                # Closing the response may not actually be sufficient to close
    +                # everything, so if we have a hold of the connection close that
    +                # too.
    +                if self._connection:
    +                    self._connection.close()
    +
    +            # If we hold the original response but it's closed now, we should
    +            # return the connection back to the pool.
    +            if self._original_response and self._original_response.isclosed():
    +                self.release_conn()
    +
    +    def read(self, amt=None, decode_content=None, cache_content=False):
    +        """
    +        Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
    +        parameters: ``decode_content`` and ``cache_content``.
    +
    +        :param amt:
    +            How much of the content to read. If specified, caching is skipped
    +            because it doesn't make sense to cache partial content as the full
    +            response.
    +
    +        :param decode_content:
    +            If True, will attempt to decode the body based on the
    +            'content-encoding' header.
    +
    +        :param cache_content:
    +            If True, will save the returned data such that the same result is
    +            returned despite of the state of the underlying file object. This
    +            is useful if you want the ``.data`` property to continue working
    +            after having ``.read()`` the file object. (Overridden if ``amt`` is
    +            set.)
    +        """
    +        self._init_decoder()
    +        if decode_content is None:
    +            decode_content = self.decode_content
    +
    +        if self._fp is None:
    +            return
    +
    +        flush_decoder = False
    +        fp_closed = getattr(self._fp, "closed", False)
    +
    +        with self._error_catcher():
    +            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.
    +                    # Close the connection when no data is returned
    +                    #
    +                    # This is redundant to what httplib/http.client _should_
    +                    # already do.  However, versions of python released before
    +                    # December 15, 2012 (http://bugs.python.org/issue16298) do
    +                    # not properly close the connection in all cases. There is
    +                    # no harm in redundantly calling close.
    +                    self._fp.close()
    +                    flush_decoder = True
    +                    if self.enforce_content_length and self.length_remaining not in (
    +                        0,
    +                        None,
    +                    ):
    +                        # This is an edge case that httplib failed to cover due
    +                        # to concerns of backward compatibility. We're
    +                        # addressing it here to make sure IncompleteRead is
    +                        # raised during streaming, so all calls with incorrect
    +                        # Content-Length are caught.
    +                        raise IncompleteRead(self._fp_bytes_read, self.length_remaining)
    +
    +        if data:
    +            self._fp_bytes_read += len(data)
    +            if self.length_remaining is not None:
    +                self.length_remaining -= len(data)
    +
    +            data = self._decode(data, decode_content, flush_decoder)
    +
    +            if cache_content:
    +                self._body = data
    +
    +        return data
    +
    +    def stream(self, amt=2 ** 16, decode_content=None):
    +        """
    +        A generator wrapper for the read() method. A call will block until
    +        ``amt`` bytes have been read from the connection or until the
    +        connection is closed.
    +
    +        :param amt:
    +            How much of the content to read. The generator will return up to
    +            much data per iteration, but may return less. This is particularly
    +            likely when using compressed data. However, the empty string will
    +            never be returned.
    +
    +        :param decode_content:
    +            If True, will attempt to decode the body based on the
    +            'content-encoding' header.
    +        """
    +        if self.chunked and self.supports_chunked_reads():
    +            for line in self.read_chunked(amt, decode_content=decode_content):
    +                yield line
    +        else:
    +            while not is_fp_closed(self._fp):
    +                data = self.read(amt=amt, decode_content=decode_content)
    +
    +                if data:
    +                    yield data
    +
    +    @classmethod
    +    def from_httplib(ResponseCls, r, **response_kw):
    +        """
    +        Given an :class:`httplib.HTTPResponse` instance ``r``, return a
    +        corresponding :class:`urllib3.response.HTTPResponse` object.
    +
    +        Remaining parameters are passed to the HTTPResponse constructor, along
    +        with ``original_response=r``.
    +        """
    +        headers = r.msg
    +
    +        if not isinstance(headers, HTTPHeaderDict):
    +            if PY3:
    +                headers = HTTPHeaderDict(headers.items())
    +            else:
    +                # Python 2.7
    +                headers = HTTPHeaderDict.from_httplib(headers)
    +
    +        # HTTPResponse objects in Python 3 don't have a .strict attribute
    +        strict = getattr(r, "strict", 0)
    +        resp = ResponseCls(
    +            body=r,
    +            headers=headers,
    +            status=r.status,
    +            version=r.version,
    +            reason=r.reason,
    +            strict=strict,
    +            original_response=r,
    +            **response_kw
    +        )
    +        return resp
    +
    +    # Backwards-compatibility methods for httplib.HTTPResponse
    +    def getheaders(self):
    +        return self.headers
    +
    +    def getheader(self, name, default=None):
    +        return self.headers.get(name, default)
    +
    +    # Backwards compatibility for http.cookiejar
    +    def info(self):
    +        return self.headers
    +
    +    # Overrides from io.IOBase
    +    def close(self):
    +        if not self.closed:
    +            self._fp.close()
    +
    +        if self._connection:
    +            self._connection.close()
    +
    +        if not self.auto_close:
    +            io.IOBase.close(self)
    +
    +    @property
    +    def closed(self):
    +        if not self.auto_close:
    +            return io.IOBase.closed.__get__(self)
    +        elif self._fp is None:
    +            return True
    +        elif hasattr(self._fp, "isclosed"):
    +            return self._fp.isclosed()
    +        elif hasattr(self._fp, "closed"):
    +            return self._fp.closed
    +        else:
    +            return True
    +
    +    def fileno(self):
    +        if self._fp is None:
    +            raise IOError("HTTPResponse has no file to get a fileno from")
    +        elif hasattr(self._fp, "fileno"):
    +            return self._fp.fileno()
    +        else:
    +            raise IOError(
    +                "The file-like object this HTTPResponse is wrapped "
    +                "around has no file descriptor"
    +            )
    +
    +    def flush(self):
    +        if (
    +            self._fp is not None
    +            and hasattr(self._fp, "flush")
    +            and not getattr(self._fp, "closed", False)
    +        ):
    +            return self._fp.flush()
    +
    +    def readable(self):
    +        # This method is required for `io` module compatibility.
    +        return True
    +
    +    def readinto(self, b):
    +        # This method is required for `io` module compatibility.
    +        temp = self.read(len(b))
    +        if len(temp) == 0:
    +            return 0
    +        else:
    +            b[: len(temp)] = temp
    +            return len(temp)
    +
    +    def supports_chunked_reads(self):
    +        """
    +        Checks if the underlying file-like object looks like a
    +        httplib.HTTPResponse object. We do this by testing for the fp
    +        attribute. If it is present we assume it returns raw chunks as
    +        processed by read_chunked().
    +        """
    +        return hasattr(self._fp, "fp")
    +
    +    def _update_chunk_length(self):
    +        # First, we'll figure out length of a chunk and then
    +        # we'll try to read it from socket.
    +        if self.chunk_left is not None:
    +            return
    +        line = self._fp.fp.readline()
    +        line = line.split(b";", 1)[0]
    +        try:
    +            self.chunk_left = int(line, 16)
    +        except ValueError:
    +            # Invalid chunked protocol response, abort.
    +            self.close()
    +            raise httplib.IncompleteRead(line)
    +
    +    def _handle_chunk(self, amt):
    +        returned_chunk = None
    +        if amt is None:
    +            chunk = self._fp._safe_read(self.chunk_left)
    +            returned_chunk = chunk
    +            self._fp._safe_read(2)  # Toss the CRLF at the end of the chunk.
    +            self.chunk_left = None
    +        elif amt < self.chunk_left:
    +            value = self._fp._safe_read(amt)
    +            self.chunk_left = self.chunk_left - amt
    +            returned_chunk = value
    +        elif amt == self.chunk_left:
    +            value = self._fp._safe_read(amt)
    +            self._fp._safe_read(2)  # Toss the CRLF at the end of the chunk.
    +            self.chunk_left = None
    +            returned_chunk = value
    +        else:  # amt > self.chunk_left
    +            returned_chunk = self._fp._safe_read(self.chunk_left)
    +            self._fp._safe_read(2)  # Toss the CRLF at the end of the chunk.
    +            self.chunk_left = None
    +        return returned_chunk
    +
    +    def read_chunked(self, amt=None, decode_content=None):
    +        """
    +        Similar to :meth:`HTTPResponse.read`, but with an additional
    +        parameter: ``decode_content``.
    +
    +        :param amt:
    +            How much of the content to read. If specified, caching is skipped
    +            because it doesn't make sense to cache partial content as the full
    +            response.
    +
    +        :param decode_content:
    +            If True, will attempt to decode the body based on the
    +            'content-encoding' header.
    +        """
    +        self._init_decoder()
    +        # FIXME: Rewrite this method and make it a class with a better structured logic.
    +        if not self.chunked:
    +            raise ResponseNotChunked(
    +                "Response is not chunked. "
    +                "Header 'transfer-encoding: chunked' is missing."
    +            )
    +        if not self.supports_chunked_reads():
    +            raise BodyNotHttplibCompatible(
    +                "Body should be httplib.HTTPResponse like. "
    +                "It should have have an fp attribute which returns raw chunks."
    +            )
    +
    +        with self._error_catcher():
    +            # Don't bother reading the body of a HEAD request.
    +            if self._original_response and is_response_to_head(self._original_response):
    +                self._original_response.close()
    +                return
    +
    +            # If a response is already read and closed
    +            # then return immediately.
    +            if self._fp.fp is None:
    +                return
    +
    +            while True:
    +                self._update_chunk_length()
    +                if self.chunk_left == 0:
    +                    break
    +                chunk = self._handle_chunk(amt)
    +                decoded = self._decode(
    +                    chunk, decode_content=decode_content, flush_decoder=False
    +                )
    +                if decoded:
    +                    yield decoded
    +
    +            if decode_content:
    +                # On CPython and PyPy, we should never need to flush the
    +                # decoder. However, on Jython we *might* need to, so
    +                # lets defensively do it anyway.
    +                decoded = self._flush_decoder()
    +                if decoded:  # Platform-specific: Jython.
    +                    yield decoded
    +
    +            # Chunk content ends with \r\n: discard it.
    +            while True:
    +                line = self._fp.fp.readline()
    +                if not line:
    +                    # Some sites may not end with '\r\n'.
    +                    break
    +                if line == b"\r\n":
    +                    break
    +
    +            # We read everything; close the "file".
    +            if self._original_response:
    +                self._original_response.close()
    +
    +    def geturl(self):
    +        """
    +        Returns the URL that was the source of this response.
    +        If the request that generated this response redirected, this method
    +        will return the final redirect location.
    +        """
    +        if self.retries is not None and len(self.retries.history):
    +            return self.retries.history[-1].redirect_location
    +        else:
    +            return self._request_url
    +
    +    def __iter__(self):
    +        buffer = [b""]
    +        for chunk in self.stream(decode_content=True):
    +            if b"\n" in chunk:
    +                chunk = chunk.split(b"\n")
    +                yield b"".join(buffer) + chunk[0] + b"\n"
    +                for x in chunk[1:-1]:
    +                    yield x + b"\n"
    +                if chunk[-1]:
    +                    buffer = [chunk[-1]]
    +                else:
    +                    buffer = []
    +            else:
    +                buffer.append(chunk)
    +        if buffer:
    +            yield b"".join(buffer)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/__init__.py b/venv/lib/python3.7/site-packages/urllib3/util/__init__.py
    new file mode 100644
    index 0000000..a96c73a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/__init__.py
    @@ -0,0 +1,46 @@
    +from __future__ import absolute_import
    +
    +# For backwards compatibility, provide imports that used to be here.
    +from .connection import is_connection_dropped
    +from .request import make_headers
    +from .response import is_fp_closed
    +from .ssl_ import (
    +    SSLContext,
    +    HAS_SNI,
    +    IS_PYOPENSSL,
    +    IS_SECURETRANSPORT,
    +    assert_fingerprint,
    +    resolve_cert_reqs,
    +    resolve_ssl_version,
    +    ssl_wrap_socket,
    +    PROTOCOL_TLS,
    +)
    +from .timeout import current_time, Timeout
    +
    +from .retry import Retry
    +from .url import get_host, parse_url, split_first, Url
    +from .wait import wait_for_read, wait_for_write
    +
    +__all__ = (
    +    "HAS_SNI",
    +    "IS_PYOPENSSL",
    +    "IS_SECURETRANSPORT",
    +    "SSLContext",
    +    "PROTOCOL_TLS",
    +    "Retry",
    +    "Timeout",
    +    "Url",
    +    "assert_fingerprint",
    +    "current_time",
    +    "is_connection_dropped",
    +    "is_fp_closed",
    +    "get_host",
    +    "parse_url",
    +    "make_headers",
    +    "resolve_cert_reqs",
    +    "resolve_ssl_version",
    +    "split_first",
    +    "ssl_wrap_socket",
    +    "wait_for_read",
    +    "wait_for_write",
    +)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/connection.py b/venv/lib/python3.7/site-packages/urllib3/util/connection.py
    new file mode 100644
    index 0000000..86f0a3b
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/connection.py
    @@ -0,0 +1,138 @@
    +from __future__ import absolute_import
    +import socket
    +from .wait import NoWayToWaitForSocketError, wait_for_read
    +from ..contrib import _appengine_environ
    +
    +
    +def is_connection_dropped(conn):  # Platform-specific
    +    """
    +    Returns True if the connection is dropped and should be closed.
    +
    +    :param conn:
    +        :class:`httplib.HTTPConnection` object.
    +
    +    Note: For platforms like AppEngine, this will always return ``False`` to
    +    let the platform handle connection recycling transparently for us.
    +    """
    +    sock = getattr(conn, "sock", False)
    +    if sock is False:  # Platform-specific: AppEngine
    +        return False
    +    if sock is None:  # Connection already closed (such as by httplib).
    +        return True
    +    try:
    +        # Returns True if readable, which here means it's been dropped
    +        return wait_for_read(sock, timeout=0.0)
    +    except NoWayToWaitForSocketError:  # Platform-specific: AppEngine
    +        return False
    +
    +
    +# This function is copied from socket.py in the Python 2.7 standard
    +# library test suite. Added to its signature is only `socket_options`.
    +# One additional modification is that we avoid binding to IPv6 servers
    +# discovered in DNS if the system doesn't have IPv6 functionality.
    +def create_connection(
    +    address,
    +    timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
    +    source_address=None,
    +    socket_options=None,
    +):
    +    """Connect to *address* and return the socket object.
    +
    +    Convenience function.  Connect to *address* (a 2-tuple ``(host,
    +    port)``) and return the socket object.  Passing the optional
    +    *timeout* parameter will set the timeout on the socket instance
    +    before attempting to connect.  If no *timeout* is supplied, the
    +    global default timeout setting returned by :func:`getdefaulttimeout`
    +    is used.  If *source_address* is set it must be a tuple of (host, port)
    +    for the socket to bind as a source address before making the connection.
    +    An host of '' or port 0 tells the OS to use the default.
    +    """
    +
    +    host, port = address
    +    if host.startswith("["):
    +        host = host.strip("[]")
    +    err = None
    +
    +    # Using the value from allowed_gai_family() in the context of getaddrinfo lets
    +    # us select whether to work with IPv4 DNS records, IPv6 records, or both.
    +    # The original create_connection function always returns all records.
    +    family = allowed_gai_family()
    +
    +    for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
    +        af, socktype, proto, canonname, sa = res
    +        sock = None
    +        try:
    +            sock = socket.socket(af, socktype, proto)
    +
    +            # If provided, set socket level options before connecting.
    +            _set_socket_options(sock, socket_options)
    +
    +            if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
    +                sock.settimeout(timeout)
    +            if source_address:
    +                sock.bind(source_address)
    +            sock.connect(sa)
    +            return sock
    +
    +        except socket.error as e:
    +            err = e
    +            if sock is not None:
    +                sock.close()
    +                sock = None
    +
    +    if err is not None:
    +        raise err
    +
    +    raise socket.error("getaddrinfo returns an empty list")
    +
    +
    +def _set_socket_options(sock, options):
    +    if options is None:
    +        return
    +
    +    for opt in options:
    +        sock.setsockopt(*opt)
    +
    +
    +def allowed_gai_family():
    +    """This function is designed to work in the context of
    +    getaddrinfo, where family=socket.AF_UNSPEC is the default and
    +    will perform a DNS search for both IPv6 and IPv4 records."""
    +
    +    family = socket.AF_INET
    +    if HAS_IPV6:
    +        family = socket.AF_UNSPEC
    +    return family
    +
    +
    +def _has_ipv6(host):
    +    """ Returns True if the system can bind an IPv6 address. """
    +    sock = None
    +    has_ipv6 = False
    +
    +    # App Engine doesn't support IPV6 sockets and actually has a quota on the
    +    # number of sockets that can be used, so just early out here instead of
    +    # creating a socket needlessly.
    +    # See https://github.com/urllib3/urllib3/issues/1446
    +    if _appengine_environ.is_appengine_sandbox():
    +        return False
    +
    +    if socket.has_ipv6:
    +        # has_ipv6 returns true if cPython was compiled with IPv6 support.
    +        # It does not tell us if the system has IPv6 support enabled. To
    +        # determine that we must bind to an IPv6 address.
    +        # https://github.com/urllib3/urllib3/pull/611
    +        # https://bugs.python.org/issue658327
    +        try:
    +            sock = socket.socket(socket.AF_INET6)
    +            sock.bind((host, 0))
    +            has_ipv6 = True
    +        except Exception:
    +            pass
    +
    +    if sock:
    +        sock.close()
    +    return has_ipv6
    +
    +
    +HAS_IPV6 = _has_ipv6("::1")
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/queue.py b/venv/lib/python3.7/site-packages/urllib3/util/queue.py
    new file mode 100644
    index 0000000..d3d379a
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/queue.py
    @@ -0,0 +1,21 @@
    +import collections
    +from ..packages import six
    +from ..packages.six.moves import queue
    +
    +if six.PY2:
    +    # Queue is imported for side effects on MS Windows. See issue #229.
    +    import Queue as _unused_module_Queue  # noqa: F401
    +
    +
    +class LifoQueue(queue.Queue):
    +    def _init(self, _):
    +        self.queue = collections.deque()
    +
    +    def _qsize(self, len=len):
    +        return len(self.queue)
    +
    +    def _put(self, item):
    +        self.queue.append(item)
    +
    +    def _get(self):
    +        return self.queue.pop()
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/request.py b/venv/lib/python3.7/site-packages/urllib3/util/request.py
    new file mode 100644
    index 0000000..3b7bb54
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/request.py
    @@ -0,0 +1,135 @@
    +from __future__ import absolute_import
    +from base64 import b64encode
    +
    +from ..packages.six import b, integer_types
    +from ..exceptions import UnrewindableBodyError
    +
    +ACCEPT_ENCODING = "gzip,deflate"
    +try:
    +    import brotli as _unused_module_brotli  # noqa: F401
    +except ImportError:
    +    pass
    +else:
    +    ACCEPT_ENCODING += ",br"
    +
    +_FAILEDTELL = object()
    +
    +
    +def make_headers(
    +    keep_alive=None,
    +    accept_encoding=None,
    +    user_agent=None,
    +    basic_auth=None,
    +    proxy_basic_auth=None,
    +    disable_cache=None,
    +):
    +    """
    +    Shortcuts for generating request headers.
    +
    +    :param keep_alive:
    +        If ``True``, adds 'connection: keep-alive' header.
    +
    +    :param accept_encoding:
    +        Can be a boolean, list, or string.
    +        ``True`` translates to 'gzip,deflate'.
    +        List will get joined by comma.
    +        String will be used as provided.
    +
    +    :param user_agent:
    +        String representing the user-agent you want, such as
    +        "python-urllib3/0.6"
    +
    +    :param basic_auth:
    +        Colon-separated username:password string for 'authorization: basic ...'
    +        auth header.
    +
    +    :param proxy_basic_auth:
    +        Colon-separated username:password string for 'proxy-authorization: basic ...'
    +        auth header.
    +
    +    :param disable_cache:
    +        If ``True``, adds 'cache-control: no-cache' header.
    +
    +    Example::
    +
    +        >>> make_headers(keep_alive=True, user_agent="Batman/1.0")
    +        {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
    +        >>> make_headers(accept_encoding=True)
    +        {'accept-encoding': 'gzip,deflate'}
    +    """
    +    headers = {}
    +    if accept_encoding:
    +        if isinstance(accept_encoding, str):
    +            pass
    +        elif isinstance(accept_encoding, list):
    +            accept_encoding = ",".join(accept_encoding)
    +        else:
    +            accept_encoding = ACCEPT_ENCODING
    +        headers["accept-encoding"] = accept_encoding
    +
    +    if user_agent:
    +        headers["user-agent"] = user_agent
    +
    +    if keep_alive:
    +        headers["connection"] = "keep-alive"
    +
    +    if basic_auth:
    +        headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8")
    +
    +    if proxy_basic_auth:
    +        headers["proxy-authorization"] = "Basic " + b64encode(
    +            b(proxy_basic_auth)
    +        ).decode("utf-8")
    +
    +    if disable_cache:
    +        headers["cache-control"] = "no-cache"
    +
    +    return headers
    +
    +
    +def set_file_position(body, pos):
    +    """
    +    If a position is provided, move file to that point.
    +    Otherwise, we'll attempt to record a position for future use.
    +    """
    +    if pos is not None:
    +        rewind_body(body, pos)
    +    elif getattr(body, "tell", None) is not None:
    +        try:
    +            pos = body.tell()
    +        except (IOError, OSError):
    +            # This differentiates from None, allowing us to catch
    +            # a failed `tell()` later when trying to rewind the body.
    +            pos = _FAILEDTELL
    +
    +    return pos
    +
    +
    +def rewind_body(body, body_pos):
    +    """
    +    Attempt to rewind body to a certain position.
    +    Primarily used for request redirects and retries.
    +
    +    :param body:
    +        File-like object that supports seek.
    +
    +    :param int pos:
    +        Position to seek to in file.
    +    """
    +    body_seek = getattr(body, "seek", None)
    +    if body_seek is not None and isinstance(body_pos, integer_types):
    +        try:
    +            body_seek(body_pos)
    +        except (IOError, OSError):
    +            raise UnrewindableBodyError(
    +                "An error occurred when rewinding request body for redirect/retry."
    +            )
    +    elif body_pos is _FAILEDTELL:
    +        raise UnrewindableBodyError(
    +            "Unable to record file position for rewinding "
    +            "request body during a redirect/retry."
    +        )
    +    else:
    +        raise ValueError(
    +            "body_pos must be of type integer, instead it was %s." % type(body_pos)
    +        )
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/response.py b/venv/lib/python3.7/site-packages/urllib3/util/response.py
    new file mode 100644
    index 0000000..715868d
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/response.py
    @@ -0,0 +1,86 @@
    +from __future__ import absolute_import
    +from ..packages.six.moves import http_client as httplib
    +
    +from ..exceptions import HeaderParsingError
    +
    +
    +def is_fp_closed(obj):
    +    """
    +    Checks whether a given file-like object is closed.
    +
    +    :param obj:
    +        The file-like object to check.
    +    """
    +
    +    try:
    +        # Check `isclosed()` first, in case Python3 doesn't set `closed`.
    +        # GH Issue #928
    +        return obj.isclosed()
    +    except AttributeError:
    +        pass
    +
    +    try:
    +        # Check via the official file-like-object way.
    +        return obj.closed
    +    except AttributeError:
    +        pass
    +
    +    try:
    +        # Check if the object is a container for another file-like object that
    +        # gets released on exhaustion (e.g. HTTPResponse).
    +        return obj.fp is None
    +    except AttributeError:
    +        pass
    +
    +    raise ValueError("Unable to determine whether fp is closed.")
    +
    +
    +def assert_header_parsing(headers):
    +    """
    +    Asserts whether all headers have been successfully parsed.
    +    Extracts encountered errors from the result of parsing headers.
    +
    +    Only works on Python 3.
    +
    +    :param headers: Headers to verify.
    +    :type headers: `httplib.HTTPMessage`.
    +
    +    :raises urllib3.exceptions.HeaderParsingError:
    +        If parsing errors are found.
    +    """
    +
    +    # This will fail silently if we pass in the wrong kind of parameter.
    +    # To make debugging easier add an explicit check.
    +    if not isinstance(headers, httplib.HTTPMessage):
    +        raise TypeError("expected httplib.Message, got {0}.".format(type(headers)))
    +
    +    defects = getattr(headers, "defects", None)
    +    get_payload = getattr(headers, "get_payload", None)
    +
    +    unparsed_data = None
    +    if get_payload:
    +        # get_payload is actually email.message.Message.get_payload;
    +        # we're only interested in the result if it's not a multipart message
    +        if not headers.is_multipart():
    +            payload = get_payload()
    +
    +            if isinstance(payload, (bytes, str)):
    +                unparsed_data = payload
    +
    +    if defects or unparsed_data:
    +        raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data)
    +
    +
    +def is_response_to_head(response):
    +    """
    +    Checks whether the request of a response has been a HEAD-request.
    +    Handles the quirks of AppEngine.
    +
    +    :param conn:
    +    :type conn: :class:`httplib.HTTPResponse`
    +    """
    +    # FIXME: Can we do this somehow without accessing private httplib _method?
    +    method = response._method
    +    if isinstance(method, int):  # Platform-specific: Appengine
    +        return method == 3
    +    return method.upper() == "HEAD"
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/retry.py b/venv/lib/python3.7/site-packages/urllib3/util/retry.py
    new file mode 100644
    index 0000000..5a049fe
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/retry.py
    @@ -0,0 +1,450 @@
    +from __future__ import absolute_import
    +import time
    +import logging
    +from collections import namedtuple
    +from itertools import takewhile
    +import email
    +import re
    +
    +from ..exceptions import (
    +    ConnectTimeoutError,
    +    MaxRetryError,
    +    ProtocolError,
    +    ReadTimeoutError,
    +    ResponseError,
    +    InvalidHeader,
    +)
    +from ..packages import six
    +
    +
    +log = logging.getLogger(__name__)
    +
    +
    +# Data structure for representing the metadata of requests that result in a retry.
    +RequestHistory = namedtuple(
    +    "RequestHistory", ["method", "url", "error", "status", "redirect_location"]
    +)
    +
    +
    +class Retry(object):
    +    """ Retry configuration.
    +
    +    Each retry attempt will create a new Retry object with updated values, so
    +    they can be safely reused.
    +
    +    Retries can be defined as a default for a pool::
    +
    +        retries = Retry(connect=5, read=2, redirect=5)
    +        http = PoolManager(retries=retries)
    +        response = http.request('GET', 'http://example.com/')
    +
    +    Or per-request (which overrides the default for the pool)::
    +
    +        response = http.request('GET', 'http://example.com/', retries=Retry(10))
    +
    +    Retries can be disabled by passing ``False``::
    +
    +        response = http.request('GET', 'http://example.com/', retries=False)
    +
    +    Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless
    +    retries are disabled, in which case the causing exception will be raised.
    +
    +    :param int total:
    +        Total number of retries to allow. Takes precedence over other counts.
    +
    +        Set to ``None`` to remove this constraint and fall back on other
    +        counts. It's a good idea to set this to some sensibly-high value to
    +        account for unexpected edge cases and avoid infinite retry loops.
    +
    +        Set to ``0`` to fail on the first retry.
    +
    +        Set to ``False`` to disable and imply ``raise_on_redirect=False``.
    +
    +    :param int connect:
    +        How many connection-related errors to retry on.
    +
    +        These are errors raised before the request is sent to the remote server,
    +        which we assume has not triggered the server to process the request.
    +
    +        Set to ``0`` to fail on the first retry of this type.
    +
    +    :param int read:
    +        How many times to retry on read errors.
    +
    +        These errors are raised after the request was sent to the server, so the
    +        request may have side-effects.
    +
    +        Set to ``0`` to fail on the first retry of this type.
    +
    +    :param int redirect:
    +        How many redirects to perform. Limit this to avoid infinite redirect
    +        loops.
    +
    +        A redirect is a HTTP response with a status code 301, 302, 303, 307 or
    +        308.
    +
    +        Set to ``0`` to fail on the first retry of this type.
    +
    +        Set to ``False`` to disable and imply ``raise_on_redirect=False``.
    +
    +    :param int status:
    +        How many times to retry on bad status codes.
    +
    +        These are retries made on responses, where status code matches
    +        ``status_forcelist``.
    +
    +        Set to ``0`` to fail on the first retry of this type.
    +
    +    :param iterable method_whitelist:
    +        Set of uppercased HTTP method verbs that we should retry on.
    +
    +        By default, we only retry on methods which are considered to be
    +        idempotent (multiple requests with the same parameters end with the
    +        same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`.
    +
    +        Set to a ``False`` value to retry on any verb.
    +
    +    :param iterable status_forcelist:
    +        A set of integer HTTP status codes that we should force a retry on.
    +        A retry is initiated if the request method is in ``method_whitelist``
    +        and the response status code is in ``status_forcelist``.
    +
    +        By default, this is disabled with ``None``.
    +
    +    :param float backoff_factor:
    +        A backoff factor to apply between attempts after the second try
    +        (most errors are resolved immediately by a second try without a
    +        delay). urllib3 will sleep for::
    +
    +            {backoff factor} * (2 ** ({number of total retries} - 1))
    +
    +        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`.
    +
    +        By default, backoff is disabled (set to 0).
    +
    +    :param bool raise_on_redirect: Whether, if the number of redirects is
    +        exhausted, to raise a MaxRetryError, or to return a response with a
    +        response code in the 3xx range.
    +
    +    :param bool raise_on_status: Similar meaning to ``raise_on_redirect``:
    +        whether we should raise an exception, or return a response,
    +        if status falls in ``status_forcelist`` range and retries have
    +        been exhausted.
    +
    +    :param tuple history: The history of the request encountered during
    +        each call to :meth:`~Retry.increment`. The list is in the order
    +        the requests occurred. Each list item is of class :class:`RequestHistory`.
    +
    +    :param bool respect_retry_after_header:
    +        Whether to respect Retry-After header on status codes defined as
    +        :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not.
    +
    +    :param iterable remove_headers_on_redirect:
    +        Sequence of headers to remove from the request when a response
    +        indicating a redirect is returned before firing off the redirected
    +        request.
    +    """
    +
    +    DEFAULT_METHOD_WHITELIST = frozenset(
    +        ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]
    +    )
    +
    +    RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503])
    +
    +    DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(["Authorization"])
    +
    +    #: Maximum backoff time.
    +    BACKOFF_MAX = 120
    +
    +    def __init__(
    +        self,
    +        total=10,
    +        connect=None,
    +        read=None,
    +        redirect=None,
    +        status=None,
    +        method_whitelist=DEFAULT_METHOD_WHITELIST,
    +        status_forcelist=None,
    +        backoff_factor=0,
    +        raise_on_redirect=True,
    +        raise_on_status=True,
    +        history=None,
    +        respect_retry_after_header=True,
    +        remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST,
    +    ):
    +
    +        self.total = total
    +        self.connect = connect
    +        self.read = read
    +        self.status = status
    +
    +        if redirect is False or total is False:
    +            redirect = 0
    +            raise_on_redirect = False
    +
    +        self.redirect = redirect
    +        self.status_forcelist = status_forcelist or set()
    +        self.method_whitelist = method_whitelist
    +        self.backoff_factor = backoff_factor
    +        self.raise_on_redirect = raise_on_redirect
    +        self.raise_on_status = raise_on_status
    +        self.history = history or tuple()
    +        self.respect_retry_after_header = respect_retry_after_header
    +        self.remove_headers_on_redirect = frozenset(
    +            [h.lower() for h in remove_headers_on_redirect]
    +        )
    +
    +    def new(self, **kw):
    +        params = dict(
    +            total=self.total,
    +            connect=self.connect,
    +            read=self.read,
    +            redirect=self.redirect,
    +            status=self.status,
    +            method_whitelist=self.method_whitelist,
    +            status_forcelist=self.status_forcelist,
    +            backoff_factor=self.backoff_factor,
    +            raise_on_redirect=self.raise_on_redirect,
    +            raise_on_status=self.raise_on_status,
    +            history=self.history,
    +            remove_headers_on_redirect=self.remove_headers_on_redirect,
    +            respect_retry_after_header=self.respect_retry_after_header,
    +        )
    +        params.update(kw)
    +        return type(self)(**params)
    +
    +    @classmethod
    +    def from_int(cls, retries, redirect=True, default=None):
    +        """ Backwards-compatibility for the old retries format."""
    +        if retries is None:
    +            retries = default if default is not None else cls.DEFAULT
    +
    +        if isinstance(retries, Retry):
    +            return retries
    +
    +        redirect = bool(redirect) and None
    +        new_retries = cls(retries, redirect=redirect)
    +        log.debug("Converted retries value: %r -> %r", retries, new_retries)
    +        return new_retries
    +
    +    def get_backoff_time(self):
    +        """ Formula for computing the current backoff
    +
    +        :rtype: float
    +        """
    +        # We want to consider only the last consecutive errors sequence (Ignore redirects).
    +        consecutive_errors_len = len(
    +            list(
    +                takewhile(lambda x: x.redirect_location is None, reversed(self.history))
    +            )
    +        )
    +        if consecutive_errors_len <= 1:
    +            return 0
    +
    +        backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1))
    +        return min(self.BACKOFF_MAX, backoff_value)
    +
    +    def parse_retry_after(self, retry_after):
    +        # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4
    +        if re.match(r"^\s*[0-9]+\s*$", retry_after):
    +            seconds = int(retry_after)
    +        else:
    +            retry_date_tuple = email.utils.parsedate(retry_after)
    +            if retry_date_tuple is None:
    +                raise InvalidHeader("Invalid Retry-After header: %s" % retry_after)
    +            retry_date = time.mktime(retry_date_tuple)
    +            seconds = retry_date - time.time()
    +
    +        if seconds < 0:
    +            seconds = 0
    +
    +        return seconds
    +
    +    def get_retry_after(self, response):
    +        """ Get the value of Retry-After in seconds. """
    +
    +        retry_after = response.getheader("Retry-After")
    +
    +        if retry_after is None:
    +            return None
    +
    +        return self.parse_retry_after(retry_after)
    +
    +    def sleep_for_retry(self, response=None):
    +        retry_after = self.get_retry_after(response)
    +        if retry_after:
    +            time.sleep(retry_after)
    +            return True
    +
    +        return False
    +
    +    def _sleep_backoff(self):
    +        backoff = self.get_backoff_time()
    +        if backoff <= 0:
    +            return
    +        time.sleep(backoff)
    +
    +    def sleep(self, response=None):
    +        """ Sleep between retry attempts.
    +
    +        This method will respect a server's ``Retry-After`` response header
    +        and sleep the duration of the time requested. If that is not present, it
    +        will use an exponential backoff. By default, the backoff factor is 0 and
    +        this method will return immediately.
    +        """
    +
    +        if self.respect_retry_after_header and response:
    +            slept = self.sleep_for_retry(response)
    +            if slept:
    +                return
    +
    +        self._sleep_backoff()
    +
    +    def _is_connection_error(self, err):
    +        """ Errors when we're fairly sure that the server did not receive the
    +        request, so it should be safe to retry.
    +        """
    +        return isinstance(err, ConnectTimeoutError)
    +
    +    def _is_read_error(self, err):
    +        """ Errors that occur after the request has been started, so we should
    +        assume that the server began processing it.
    +        """
    +        return isinstance(err, (ReadTimeoutError, ProtocolError))
    +
    +    def _is_method_retryable(self, method):
    +        """ Checks if a given HTTP method should be retried upon, depending if
    +        it is included on the method whitelist.
    +        """
    +        if self.method_whitelist and method.upper() not in self.method_whitelist:
    +            return False
    +
    +        return True
    +
    +    def is_retry(self, method, status_code, has_retry_after=False):
    +        """ Is this method/status code retryable? (Based on whitelists and control
    +        variables such as the number of total retries to allow, whether to
    +        respect the Retry-After header, whether this header is present, and
    +        whether the returned status code is on the list of status codes to
    +        be retried upon on the presence of the aforementioned header)
    +        """
    +        if not self._is_method_retryable(method):
    +            return False
    +
    +        if self.status_forcelist and status_code in self.status_forcelist:
    +            return True
    +
    +        return (
    +            self.total
    +            and self.respect_retry_after_header
    +            and has_retry_after
    +            and (status_code in self.RETRY_AFTER_STATUS_CODES)
    +        )
    +
    +    def is_exhausted(self):
    +        """ Are we out of retries? """
    +        retry_counts = (self.total, self.connect, self.read, self.redirect, self.status)
    +        retry_counts = list(filter(None, retry_counts))
    +        if not retry_counts:
    +            return False
    +
    +        return min(retry_counts) < 0
    +
    +    def increment(
    +        self,
    +        method=None,
    +        url=None,
    +        response=None,
    +        error=None,
    +        _pool=None,
    +        _stacktrace=None,
    +    ):
    +        """ Return a new Retry object with incremented retry counters.
    +
    +        :param response: A response object, or None, if the server did not
    +            return a response.
    +        :type response: :class:`~urllib3.response.HTTPResponse`
    +        :param Exception error: An error encountered during the request, or
    +            None if the response was received successfully.
    +
    +        :return: A new ``Retry`` object.
    +        """
    +        if self.total is False and error:
    +            # Disabled, indicate to re-raise the error.
    +            raise six.reraise(type(error), error, _stacktrace)
    +
    +        total = self.total
    +        if total is not None:
    +            total -= 1
    +
    +        connect = self.connect
    +        read = self.read
    +        redirect = self.redirect
    +        status_count = self.status
    +        cause = "unknown"
    +        status = None
    +        redirect_location = None
    +
    +        if error and self._is_connection_error(error):
    +            # Connect retry?
    +            if connect is False:
    +                raise six.reraise(type(error), error, _stacktrace)
    +            elif connect is not None:
    +                connect -= 1
    +
    +        elif error and self._is_read_error(error):
    +            # Read retry?
    +            if read is False or not self._is_method_retryable(method):
    +                raise six.reraise(type(error), error, _stacktrace)
    +            elif read is not None:
    +                read -= 1
    +
    +        elif response and response.get_redirect_location():
    +            # Redirect retry?
    +            if redirect is not None:
    +                redirect -= 1
    +            cause = "too many redirects"
    +            redirect_location = response.get_redirect_location()
    +            status = response.status
    +
    +        else:
    +            # Incrementing because of a server error like a 500 in
    +            # status_forcelist and a the given method is in the whitelist
    +            cause = ResponseError.GENERIC_ERROR
    +            if response and response.status:
    +                if status_count is not None:
    +                    status_count -= 1
    +                cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status)
    +                status = response.status
    +
    +        history = self.history + (
    +            RequestHistory(method, url, error, status, redirect_location),
    +        )
    +
    +        new_retry = self.new(
    +            total=total,
    +            connect=connect,
    +            read=read,
    +            redirect=redirect,
    +            status=status_count,
    +            history=history,
    +        )
    +
    +        if new_retry.is_exhausted():
    +            raise MaxRetryError(_pool, url, error or ResponseError(cause))
    +
    +        log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)
    +
    +        return new_retry
    +
    +    def __repr__(self):
    +        return (
    +            "{cls.__name__}(total={self.total}, connect={self.connect}, "
    +            "read={self.read}, redirect={self.redirect}, status={self.status})"
    +        ).format(cls=type(self), self=self)
    +
    +
    +# For backwards compatibility (equivalent to pre-v1.9):
    +Retry.DEFAULT = Retry(3)
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/ssl_.py b/venv/lib/python3.7/site-packages/urllib3/util/ssl_.py
    new file mode 100644
    index 0000000..8495b77
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/ssl_.py
    @@ -0,0 +1,407 @@
    +from __future__ import absolute_import
    +import errno
    +import warnings
    +import hmac
    +import sys
    +
    +from binascii import hexlify, unhexlify
    +from hashlib import md5, sha1, sha256
    +
    +from .url import IPV4_RE, BRACELESS_IPV6_ADDRZ_RE
    +from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
    +from ..packages import six
    +
    +
    +SSLContext = None
    +HAS_SNI = False
    +IS_PYOPENSSL = False
    +IS_SECURETRANSPORT = False
    +
    +# Maps the length of a digest to a possible hash function producing this digest
    +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256}
    +
    +
    +def _const_compare_digest_backport(a, b):
    +    """
    +    Compare two digests of equal length in constant time.
    +
    +    The digests must be of type str/bytes.
    +    Returns True if the digests match, and False otherwise.
    +    """
    +    result = abs(len(a) - len(b))
    +    for l, r in zip(bytearray(a), bytearray(b)):
    +        result |= l ^ r
    +    return result == 0
    +
    +
    +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport)
    +
    +try:  # Test for SSL features
    +    import ssl
    +    from ssl import wrap_socket, CERT_REQUIRED
    +    from ssl import HAS_SNI  # Has SNI?
    +except ImportError:
    +    pass
    +
    +try:  # Platform-specific: Python 3.6
    +    from ssl import PROTOCOL_TLS
    +
    +    PROTOCOL_SSLv23 = PROTOCOL_TLS
    +except ImportError:
    +    try:
    +        from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS
    +
    +        PROTOCOL_SSLv23 = PROTOCOL_TLS
    +    except ImportError:
    +        PROTOCOL_SSLv23 = PROTOCOL_TLS = 2
    +
    +
    +try:
    +    from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION
    +except ImportError:
    +    OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
    +    OP_NO_COMPRESSION = 0x20000
    +
    +
    +# A secure default.
    +# Sources for more information on TLS ciphers:
    +#
    +# - https://wiki.mozilla.org/Security/Server_Side_TLS
    +# - https://www.ssllabs.com/projects/best-practices/index.html
    +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
    +#
    +# The general intent is:
    +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
    +# - prefer ECDHE over DHE for better performance,
    +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and
    +#   security,
    +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
    +# - disable NULL authentication, MD5 MACs, DSS, and other
    +#   insecure ciphers for security reasons.
    +# - NOTE: TLS 1.3 cipher suites are managed through a different interface
    +#   not exposed by CPython (yet!) and are enabled by default if they're available.
    +DEFAULT_CIPHERS = ":".join(
    +    [
    +        "ECDHE+AESGCM",
    +        "ECDHE+CHACHA20",
    +        "DHE+AESGCM",
    +        "DHE+CHACHA20",
    +        "ECDH+AESGCM",
    +        "DH+AESGCM",
    +        "ECDH+AES",
    +        "DH+AES",
    +        "RSA+AESGCM",
    +        "RSA+AES",
    +        "!aNULL",
    +        "!eNULL",
    +        "!MD5",
    +        "!DSS",
    +    ]
    +)
    +
    +try:
    +    from ssl import SSLContext  # Modern SSL?
    +except ImportError:
    +
    +    class SSLContext(object):  # Platform-specific: Python 2
    +        def __init__(self, protocol_version):
    +            self.protocol = protocol_version
    +            # Use default values from a real SSLContext
    +            self.check_hostname = False
    +            self.verify_mode = ssl.CERT_NONE
    +            self.ca_certs = None
    +            self.options = 0
    +            self.certfile = None
    +            self.keyfile = None
    +            self.ciphers = None
    +
    +        def load_cert_chain(self, certfile, keyfile):
    +            self.certfile = certfile
    +            self.keyfile = keyfile
    +
    +        def load_verify_locations(self, cafile=None, capath=None):
    +            self.ca_certs = cafile
    +
    +            if capath is not None:
    +                raise SSLError("CA directories not supported in older Pythons")
    +
    +        def set_ciphers(self, cipher_suite):
    +            self.ciphers = cipher_suite
    +
    +        def wrap_socket(self, socket, server_hostname=None, server_side=False):
    +            warnings.warn(
    +                "A true SSLContext object is not available. This prevents "
    +                "urllib3 from configuring SSL appropriately and may cause "
    +                "certain SSL connections to fail. You can upgrade to a newer "
    +                "version of Python to solve this. For more information, see "
    +                "https://urllib3.readthedocs.io/en/latest/advanced-usage.html"
    +                "#ssl-warnings",
    +                InsecurePlatformWarning,
    +            )
    +            kwargs = {
    +                "keyfile": self.keyfile,
    +                "certfile": self.certfile,
    +                "ca_certs": self.ca_certs,
    +                "cert_reqs": self.verify_mode,
    +                "ssl_version": self.protocol,
    +                "server_side": server_side,
    +            }
    +            return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
    +
    +
    +def assert_fingerprint(cert, fingerprint):
    +    """
    +    Checks if given fingerprint matches the supplied certificate.
    +
    +    :param cert:
    +        Certificate as bytes object.
    +    :param fingerprint:
    +        Fingerprint as string of hexdigits, can be interspersed by colons.
    +    """
    +
    +    fingerprint = fingerprint.replace(":", "").lower()
    +    digest_length = len(fingerprint)
    +    hashfunc = HASHFUNC_MAP.get(digest_length)
    +    if not hashfunc:
    +        raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint))
    +
    +    # We need encode() here for py32; works on py2 and p33.
    +    fingerprint_bytes = unhexlify(fingerprint.encode())
    +
    +    cert_digest = hashfunc(cert).digest()
    +
    +    if not _const_compare_digest(cert_digest, fingerprint_bytes):
    +        raise SSLError(
    +            'Fingerprints did not match. Expected "{0}", got "{1}".'.format(
    +                fingerprint, hexlify(cert_digest)
    +            )
    +        )
    +
    +
    +def resolve_cert_reqs(candidate):
    +    """
    +    Resolves the argument to a numeric constant, which can be passed to
    +    the wrap_socket function/method from the ssl module.
    +    Defaults to :data:`ssl.CERT_NONE`.
    +    If given a string it is assumed to be the name of the constant in the
    +    :mod:`ssl` module or its abbreviation.
    +    (So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
    +    If it's neither `None` nor a string we assume it is already the numeric
    +    constant which can directly be passed to wrap_socket.
    +    """
    +    if candidate is None:
    +        return CERT_REQUIRED
    +
    +    if isinstance(candidate, str):
    +        res = getattr(ssl, candidate, None)
    +        if res is None:
    +            res = getattr(ssl, "CERT_" + candidate)
    +        return res
    +
    +    return candidate
    +
    +
    +def resolve_ssl_version(candidate):
    +    """
    +    like resolve_cert_reqs
    +    """
    +    if candidate is None:
    +        return PROTOCOL_TLS
    +
    +    if isinstance(candidate, str):
    +        res = getattr(ssl, candidate, None)
    +        if res is None:
    +            res = getattr(ssl, "PROTOCOL_" + candidate)
    +        return res
    +
    +    return candidate
    +
    +
    +def create_urllib3_context(
    +    ssl_version=None, cert_reqs=None, options=None, ciphers=None
    +):
    +    """All arguments have the same meaning as ``ssl_wrap_socket``.
    +
    +    By default, this function does a lot of the same work that
    +    ``ssl.create_default_context`` does on Python 3.4+. It:
    +
    +    - Disables SSLv2, SSLv3, and compression
    +    - Sets a restricted set of server ciphers
    +
    +    If you wish to enable SSLv3, you can do::
    +
    +        from urllib3.util import ssl_
    +        context = ssl_.create_urllib3_context()
    +        context.options &= ~ssl_.OP_NO_SSLv3
    +
    +    You can do the same to enable compression (substituting ``COMPRESSION``
    +    for ``SSLv3`` in the last line above).
    +
    +    :param ssl_version:
    +        The desired protocol version to use. This will default to
    +        PROTOCOL_SSLv23 which will negotiate the highest protocol that both
    +        the server and your installation of OpenSSL support.
    +    :param cert_reqs:
    +        Whether to require the certificate verification. This defaults to
    +        ``ssl.CERT_REQUIRED``.
    +    :param options:
    +        Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
    +        ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``.
    +    :param ciphers:
    +        Which cipher suites to allow the server to select.
    +    :returns:
    +        Constructed SSLContext object with specified options
    +    :rtype: SSLContext
    +    """
    +    context = SSLContext(ssl_version or PROTOCOL_TLS)
    +
    +    context.set_ciphers(ciphers or DEFAULT_CIPHERS)
    +
    +    # Setting the default here, as we may have no ssl module on import
    +    cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
    +
    +    if options is None:
    +        options = 0
    +        # SSLv2 is easily broken and is considered harmful and dangerous
    +        options |= OP_NO_SSLv2
    +        # SSLv3 has several problems and is now dangerous
    +        options |= OP_NO_SSLv3
    +        # Disable compression to prevent CRIME attacks for OpenSSL 1.0+
    +        # (issue #309)
    +        options |= OP_NO_COMPRESSION
    +
    +    context.options |= options
    +
    +    # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
    +    # necessary for conditional client cert authentication with TLS 1.3.
    +    # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older
    +    # versions of Python.  We only enable on Python 3.7.4+ or if certificate
    +    # verification is enabled to work around Python issue #37428
    +    # See: https://bugs.python.org/issue37428
    +    if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr(
    +        context, "post_handshake_auth", None
    +    ) is not None:
    +        context.post_handshake_auth = True
    +
    +    context.verify_mode = cert_reqs
    +    if (
    +        getattr(context, "check_hostname", None) is not None
    +    ):  # Platform-specific: Python 3.2
    +        # We do our own verification, including fingerprints and alternative
    +        # hostnames. So disable it here
    +        context.check_hostname = False
    +    return context
    +
    +
    +def ssl_wrap_socket(
    +    sock,
    +    keyfile=None,
    +    certfile=None,
    +    cert_reqs=None,
    +    ca_certs=None,
    +    server_hostname=None,
    +    ssl_version=None,
    +    ciphers=None,
    +    ssl_context=None,
    +    ca_cert_dir=None,
    +    key_password=None,
    +):
    +    """
    +    All arguments except for server_hostname, ssl_context, and ca_cert_dir have
    +    the same meaning as they do when using :func:`ssl.wrap_socket`.
    +
    +    :param server_hostname:
    +        When SNI is supported, the expected hostname of the certificate
    +    :param ssl_context:
    +        A pre-made :class:`SSLContext` object. If none is provided, one will
    +        be created using :func:`create_urllib3_context`.
    +    :param ciphers:
    +        A string of ciphers we wish the client to support.
    +    :param ca_cert_dir:
    +        A directory containing CA certificates in multiple separate files, as
    +        supported by OpenSSL's -CApath flag or the capath argument to
    +        SSLContext.load_verify_locations().
    +    :param key_password:
    +        Optional password if the keyfile is encrypted.
    +    """
    +    context = ssl_context
    +    if context is None:
    +        # Note: This branch of code and all the variables in it are no longer
    +        # used by urllib3 itself. We should consider deprecating and removing
    +        # this code.
    +        context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers)
    +
    +    if ca_certs or ca_cert_dir:
    +        try:
    +            context.load_verify_locations(ca_certs, ca_cert_dir)
    +        except IOError as e:  # Platform-specific: Python 2.7
    +            raise SSLError(e)
    +        # Py33 raises FileNotFoundError which subclasses OSError
    +        # These are not equivalent unless we check the errno attribute
    +        except OSError as e:  # Platform-specific: Python 3.3 and beyond
    +            if e.errno == errno.ENOENT:
    +                raise SSLError(e)
    +            raise
    +
    +    elif ssl_context is None and hasattr(context, "load_default_certs"):
    +        # try to load OS default certs; works well on Windows (require Python3.4+)
    +        context.load_default_certs()
    +
    +    # Attempt to detect if we get the goofy behavior of the
    +    # keyfile being encrypted and OpenSSL asking for the
    +    # passphrase via the terminal and instead error out.
    +    if keyfile and key_password is None and _is_key_file_encrypted(keyfile):
    +        raise SSLError("Client private key is encrypted, password is required")
    +
    +    if certfile:
    +        if key_password is None:
    +            context.load_cert_chain(certfile, keyfile)
    +        else:
    +            context.load_cert_chain(certfile, keyfile, key_password)
    +
    +    # If we detect server_hostname is an IP address then the SNI
    +    # extension should not be used according to RFC3546 Section 3.1
    +    # We shouldn't warn the user if SNI isn't available but we would
    +    # not be using SNI anyways due to IP address for server_hostname.
    +    if (
    +        server_hostname is not None and not is_ipaddress(server_hostname)
    +    ) or IS_SECURETRANSPORT:
    +        if HAS_SNI and server_hostname is not None:
    +            return context.wrap_socket(sock, server_hostname=server_hostname)
    +
    +        warnings.warn(
    +            "An HTTPS request has been made, but the SNI (Server Name "
    +            "Indication) extension to TLS is not available on this platform. "
    +            "This may cause the server to present an incorrect TLS "
    +            "certificate, which can cause validation failures. You can upgrade to "
    +            "a newer version of Python to solve this. For more information, see "
    +            "https://urllib3.readthedocs.io/en/latest/advanced-usage.html"
    +            "#ssl-warnings",
    +            SNIMissingWarning,
    +        )
    +
    +    return context.wrap_socket(sock)
    +
    +
    +def is_ipaddress(hostname):
    +    """Detects whether the hostname given is an IPv4 or IPv6 address.
    +    Also detects IPv6 addresses with Zone IDs.
    +
    +    :param str hostname: Hostname to examine.
    +    :return: True if the hostname is an IP address, False otherwise.
    +    """
    +    if not six.PY2 and isinstance(hostname, bytes):
    +        # IDN A-label bytes are ASCII compatible.
    +        hostname = hostname.decode("ascii")
    +    return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname))
    +
    +
    +def _is_key_file_encrypted(key_file):
    +    """Detects if a key file is encrypted or not."""
    +    with open(key_file, "r") as f:
    +        for line in f:
    +            # Look for Proc-Type: 4,ENCRYPTED
    +            if "ENCRYPTED" in line:
    +                return True
    +
    +    return False
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/timeout.py b/venv/lib/python3.7/site-packages/urllib3/util/timeout.py
    new file mode 100644
    index 0000000..9883700
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/timeout.py
    @@ -0,0 +1,258 @@
    +from __future__ import absolute_import
    +
    +# The default socket timeout, used by httplib to indicate that no timeout was
    +# specified by the user
    +from socket import _GLOBAL_DEFAULT_TIMEOUT
    +import time
    +
    +from ..exceptions import TimeoutStateError
    +
    +# A sentinel value to indicate that no timeout was specified by the user in
    +# urllib3
    +_Default = object()
    +
    +
    +# Use time.monotonic if available.
    +current_time = getattr(time, "monotonic", time.time)
    +
    +
    +class Timeout(object):
    +    """ Timeout configuration.
    +
    +    Timeouts can be defined as a default for a pool::
    +
    +        timeout = Timeout(connect=2.0, read=7.0)
    +        http = PoolManager(timeout=timeout)
    +        response = http.request('GET', 'http://example.com/')
    +
    +    Or per-request (which overrides the default for the pool)::
    +
    +        response = http.request('GET', 'http://example.com/', timeout=Timeout(10))
    +
    +    Timeouts can be disabled by setting all the parameters to ``None``::
    +
    +        no_timeout = Timeout(connect=None, read=None)
    +        response = http.request('GET', 'http://example.com/, timeout=no_timeout)
    +
    +
    +    :param total:
    +        This combines the connect and read timeouts into one; the read timeout
    +        will be set to the time leftover from the connect attempt. In the
    +        event that both a connect timeout and a total are specified, or a read
    +        timeout and a total are specified, the shorter timeout will be applied.
    +
    +        Defaults to None.
    +
    +    :type total: integer, float, or None
    +
    +    :param connect:
    +        The maximum amount of time (in seconds) to wait for a connection
    +        attempt to a server to succeed. Omitting the parameter will default the
    +        connect timeout to the system default, probably `the global default
    +        timeout in socket.py
    +        `_.
    +        None will set an infinite timeout for connection attempts.
    +
    +    :type connect: integer, float, or None
    +
    +    :param read:
    +        The maximum amount of time (in seconds) to wait between consecutive
    +        read operations for a response from the server. Omitting the parameter
    +        will default the read timeout to the system default, probably `the
    +        global default timeout in socket.py
    +        `_.
    +        None will set an infinite timeout.
    +
    +    :type read: integer, float, or None
    +
    +    .. note::
    +
    +        Many factors can affect the total amount of time for urllib3 to return
    +        an HTTP response.
    +
    +        For example, Python's DNS resolver does not obey the timeout specified
    +        on the socket. Other factors that can affect total request time include
    +        high CPU load, high swap, the program running at a low priority level,
    +        or other behaviors.
    +
    +        In addition, the read and total timeouts only measure the time between
    +        read operations on the socket connecting the client and the server,
    +        not the total amount of time for the request to return a complete
    +        response. For most requests, the timeout is raised because the server
    +        has not sent the first byte in the specified time. This is not always
    +        the case; if a server streams one byte every fifteen seconds, a timeout
    +        of 20 seconds will not trigger, even though the request will take
    +        several minutes to complete.
    +
    +        If your goal is to cut off any request after a set amount of wall clock
    +        time, consider having a second "watcher" thread to cut off a slow
    +        request.
    +    """
    +
    +    #: A sentinel object representing the default timeout value
    +    DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT
    +
    +    def __init__(self, total=None, connect=_Default, read=_Default):
    +        self._connect = self._validate_timeout(connect, "connect")
    +        self._read = self._validate_timeout(read, "read")
    +        self.total = self._validate_timeout(total, "total")
    +        self._start_connect = None
    +
    +    def __str__(self):
    +        return "%s(connect=%r, read=%r, total=%r)" % (
    +            type(self).__name__,
    +            self._connect,
    +            self._read,
    +            self.total,
    +        )
    +
    +    @classmethod
    +    def _validate_timeout(cls, value, name):
    +        """ Check that a timeout attribute is valid.
    +
    +        :param value: The timeout value to validate
    +        :param name: The name of the timeout attribute to validate. This is
    +            used to specify in error messages.
    +        :return: The validated and casted version of the given value.
    +        :raises ValueError: If it is a numeric value less than or equal to
    +            zero, or the type is not an integer, float, or None.
    +        """
    +        if value is _Default:
    +            return cls.DEFAULT_TIMEOUT
    +
    +        if value is None or value is cls.DEFAULT_TIMEOUT:
    +            return value
    +
    +        if isinstance(value, bool):
    +            raise ValueError(
    +                "Timeout cannot be a boolean value. It must "
    +                "be an int, float or None."
    +            )
    +        try:
    +            float(value)
    +        except (TypeError, ValueError):
    +            raise ValueError(
    +                "Timeout value %s was %s, but it must be an "
    +                "int, float or None." % (name, value)
    +            )
    +
    +        try:
    +            if value <= 0:
    +                raise ValueError(
    +                    "Attempted to set %s timeout to %s, but the "
    +                    "timeout cannot be set to a value less "
    +                    "than or equal to 0." % (name, value)
    +                )
    +        except TypeError:
    +            # Python 3
    +            raise ValueError(
    +                "Timeout value %s was %s, but it must be an "
    +                "int, float or None." % (name, value)
    +            )
    +
    +        return value
    +
    +    @classmethod
    +    def from_float(cls, timeout):
    +        """ Create a new Timeout from a legacy timeout value.
    +
    +        The timeout value used by httplib.py sets the same timeout on the
    +        connect(), and recv() socket requests. This creates a :class:`Timeout`
    +        object that sets the individual timeouts to the ``timeout`` value
    +        passed to this function.
    +
    +        :param timeout: The legacy timeout value.
    +        :type timeout: integer, float, sentinel default object, or None
    +        :return: Timeout object
    +        :rtype: :class:`Timeout`
    +        """
    +        return Timeout(read=timeout, connect=timeout)
    +
    +    def clone(self):
    +        """ Create a copy of the timeout object
    +
    +        Timeout properties are stored per-pool but each request needs a fresh
    +        Timeout object to ensure each one has its own start/stop configured.
    +
    +        :return: a copy of the timeout object
    +        :rtype: :class:`Timeout`
    +        """
    +        # We can't use copy.deepcopy because that will also create a new object
    +        # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to
    +        # detect the user default.
    +        return Timeout(connect=self._connect, read=self._read, total=self.total)
    +
    +    def start_connect(self):
    +        """ Start the timeout clock, used during a connect() attempt
    +
    +        :raises urllib3.exceptions.TimeoutStateError: if you attempt
    +            to start a timer that has been started already.
    +        """
    +        if self._start_connect is not None:
    +            raise TimeoutStateError("Timeout timer has already been started.")
    +        self._start_connect = current_time()
    +        return self._start_connect
    +
    +    def get_connect_duration(self):
    +        """ Gets the time elapsed since the call to :meth:`start_connect`.
    +
    +        :return: Elapsed time in seconds.
    +        :rtype: float
    +        :raises urllib3.exceptions.TimeoutStateError: if you attempt
    +            to get duration for a timer that hasn't been started.
    +        """
    +        if self._start_connect is None:
    +            raise TimeoutStateError(
    +                "Can't get connect duration for timer that has not started."
    +            )
    +        return current_time() - self._start_connect
    +
    +    @property
    +    def connect_timeout(self):
    +        """ Get the value to use when setting a connection timeout.
    +
    +        This will be a positive float or integer, the value None
    +        (never timeout), or the default system timeout.
    +
    +        :return: Connect timeout.
    +        :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
    +        """
    +        if self.total is None:
    +            return self._connect
    +
    +        if self._connect is None or self._connect is self.DEFAULT_TIMEOUT:
    +            return self.total
    +
    +        return min(self._connect, self.total)
    +
    +    @property
    +    def read_timeout(self):
    +        """ Get the value for the read timeout.
    +
    +        This assumes some time has elapsed in the connection timeout and
    +        computes the read timeout appropriately.
    +
    +        If self.total is set, the read timeout is dependent on the amount of
    +        time taken by the connect timeout. If the connection time has not been
    +        established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be
    +        raised.
    +
    +        :return: Value to use for the read timeout.
    +        :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
    +        :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect`
    +            has not yet been called on this object.
    +        """
    +        if (
    +            self.total is not None
    +            and self.total is not self.DEFAULT_TIMEOUT
    +            and self._read is not None
    +            and self._read is not self.DEFAULT_TIMEOUT
    +        ):
    +            # In case the connect timeout has not yet been established.
    +            if self._start_connect is None:
    +                return self._read
    +            return max(0, min(self.total - self.get_connect_duration(), self._read))
    +        elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT:
    +            return max(0, self.total - self.get_connect_duration())
    +        else:
    +            return self._read
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/url.py b/venv/lib/python3.7/site-packages/urllib3/util/url.py
    new file mode 100644
    index 0000000..f7568e9
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/url.py
    @@ -0,0 +1,436 @@
    +from __future__ import absolute_import
    +import re
    +from collections import namedtuple
    +
    +from ..exceptions import LocationParseError
    +from ..packages import six
    +
    +
    +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"]
    +
    +# We only want to normalize urls with an HTTP(S) scheme.
    +# urllib3 infers URLs without a scheme (None) to be http.
    +NORMALIZABLE_SCHEMES = ("http", "https", None)
    +
    +# Almost all of these patterns were derived from the
    +# 'rfc3986' module: https://github.com/python-hyper/rfc3986
    +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}")
    +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)")
    +URI_RE = re.compile(
    +    r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?"
    +    r"(?://([^/?#]*))?"
    +    r"([^?#]*)"
    +    r"(?:\?([^#]*))?"
    +    r"(?:#(.*))?$",
    +    re.UNICODE | re.DOTALL,
    +)
    +
    +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
    +HEX_PAT = "[0-9A-Fa-f]{1,4}"
    +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT)
    +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT}
    +_variations = [
    +    #                            6( h16 ":" ) ls32
    +    "(?:%(hex)s:){6}%(ls32)s",
    +    #                       "::" 5( h16 ":" ) ls32
    +    "::(?:%(hex)s:){5}%(ls32)s",
    +    # [               h16 ] "::" 4( h16 ":" ) ls32
    +    "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s",
    +    # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
    +    "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s",
    +    # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
    +    "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s",
    +    # [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
    +    "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s",
    +    # [ *4( h16 ":" ) h16 ] "::"              ls32
    +    "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s",
    +    # [ *5( h16 ":" ) h16 ] "::"              h16
    +    "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s",
    +    # [ *6( h16 ":" ) h16 ] "::"
    +    "(?:(?:%(hex)s:){0,6}%(hex)s)?::",
    +]
    +
    +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~"
    +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")"
    +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+"
    +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]"
    +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*"
    +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$")
    +
    +IPV4_RE = re.compile("^" + IPV4_PAT + "$")
    +IPV6_RE = re.compile("^" + IPV6_PAT + "$")
    +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")\]$")
    +
    +SUBAUTHORITY_PAT = (u"^(?:(.*)@)?(%s|%s|%s)(?::([0-9]{0,5}))?$") % (
    +    REG_NAME_PAT,
    +    IPV4_PAT,
    +    IPV6_ADDRZ_PAT,
    +)
    +SUBAUTHORITY_RE = re.compile(SUBAUTHORITY_PAT, re.UNICODE | re.DOTALL)
    +
    +UNRESERVED_CHARS = set(
    +    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~"
    +)
    +SUB_DELIM_CHARS = set("!$&'()*+,;=")
    +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"}
    +PATH_CHARS = USERINFO_CHARS | {"@", "/"}
    +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"}
    +
    +
    +class Url(namedtuple("Url", url_attrs)):
    +    """
    +    Data structure for representing an HTTP URL. Used as a return value for
    +    :func:`parse_url`. Both the scheme and host are normalized as they are
    +    both case-insensitive according to RFC 3986.
    +    """
    +
    +    __slots__ = ()
    +
    +    def __new__(
    +        cls,
    +        scheme=None,
    +        auth=None,
    +        host=None,
    +        port=None,
    +        path=None,
    +        query=None,
    +        fragment=None,
    +    ):
    +        if path and not path.startswith("/"):
    +            path = "/" + path
    +        if scheme is not None:
    +            scheme = scheme.lower()
    +        return super(Url, cls).__new__(
    +            cls, scheme, auth, host, port, path, query, fragment
    +        )
    +
    +    @property
    +    def hostname(self):
    +        """For backwards-compatibility with urlparse. We're nice like that."""
    +        return self.host
    +
    +    @property
    +    def request_uri(self):
    +        """Absolute path including the query string."""
    +        uri = self.path or "/"
    +
    +        if self.query is not None:
    +            uri += "?" + self.query
    +
    +        return uri
    +
    +    @property
    +    def netloc(self):
    +        """Network location including host and port"""
    +        if self.port:
    +            return "%s:%d" % (self.host, self.port)
    +        return self.host
    +
    +    @property
    +    def url(self):
    +        """
    +        Convert self into a url
    +
    +        This function should more or less round-trip with :func:`.parse_url`. The
    +        returned url may not be exactly the same as the url inputted to
    +        :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
    +        with a blank port will have : removed).
    +
    +        Example: ::
    +
    +            >>> U = parse_url('http://google.com/mail/')
    +            >>> U.url
    +            'http://google.com/mail/'
    +            >>> Url('http', 'username:password', 'host.com', 80,
    +            ... '/path', 'query', 'fragment').url
    +            'http://username:password@host.com:80/path?query#fragment'
    +        """
    +        scheme, auth, host, port, path, query, fragment = self
    +        url = u""
    +
    +        # We use "is not None" we want things to happen with empty strings (or 0 port)
    +        if scheme is not None:
    +            url += scheme + u"://"
    +        if auth is not None:
    +            url += auth + u"@"
    +        if host is not None:
    +            url += host
    +        if port is not None:
    +            url += u":" + str(port)
    +        if path is not None:
    +            url += path
    +        if query is not None:
    +            url += u"?" + query
    +        if fragment is not None:
    +            url += u"#" + fragment
    +
    +        return url
    +
    +    def __str__(self):
    +        return self.url
    +
    +
    +def split_first(s, delims):
    +    """
    +    .. deprecated:: 1.25
    +
    +    Given a string and an iterable of delimiters, split on the first found
    +    delimiter. Return two split parts and the matched delimiter.
    +
    +    If not found, then the first part is the full input string.
    +
    +    Example::
    +
    +        >>> split_first('foo/bar?baz', '?/=')
    +        ('foo', 'bar?baz', '/')
    +        >>> split_first('foo/bar?baz', '123')
    +        ('foo/bar?baz', '', None)
    +
    +    Scales linearly with number of delims. Not ideal for large number of delims.
    +    """
    +    min_idx = None
    +    min_delim = None
    +    for d in delims:
    +        idx = s.find(d)
    +        if idx < 0:
    +            continue
    +
    +        if min_idx is None or idx < min_idx:
    +            min_idx = idx
    +            min_delim = d
    +
    +    if min_idx is None or min_idx < 0:
    +        return s, "", None
    +
    +    return s[:min_idx], s[min_idx + 1 :], min_delim
    +
    +
    +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"):
    +    """Percent-encodes a URI component without reapplying
    +    onto an already percent-encoded component.
    +    """
    +    if component is None:
    +        return component
    +
    +    component = six.ensure_text(component)
    +
    +    # Try to see if the component we're encoding is already percent-encoded
    +    # so we can skip all '%' characters but still encode all others.
    +    percent_encodings = PERCENT_RE.findall(component)
    +
    +    # Normalize existing percent-encoded bytes.
    +    for enc in percent_encodings:
    +        if not enc.isupper():
    +            component = component.replace(enc, enc.upper())
    +
    +    uri_bytes = component.encode("utf-8", "surrogatepass")
    +    is_percent_encoded = len(percent_encodings) == uri_bytes.count(b"%")
    +
    +    encoded_component = bytearray()
    +
    +    for i in range(0, len(uri_bytes)):
    +        # Will return a single character bytestring on both Python 2 & 3
    +        byte = uri_bytes[i : i + 1]
    +        byte_ord = ord(byte)
    +        if (is_percent_encoded and byte == b"%") or (
    +            byte_ord < 128 and byte.decode() in allowed_chars
    +        ):
    +            encoded_component.extend(byte)
    +            continue
    +        encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper()))
    +
    +    return encoded_component.decode(encoding)
    +
    +
    +def _remove_path_dot_segments(path):
    +    # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code
    +    segments = path.split("/")  # Turn the path into a list of segments
    +    output = []  # Initialize the variable to use to store output
    +
    +    for segment in segments:
    +        # '.' is the current directory, so ignore it, it is superfluous
    +        if segment == ".":
    +            continue
    +        # Anything other than '..', should be appended to the output
    +        elif segment != "..":
    +            output.append(segment)
    +        # In this case segment == '..', if we can, we should pop the last
    +        # element
    +        elif output:
    +            output.pop()
    +
    +    # If the path starts with '/' and the output is empty or the first string
    +    # is non-empty
    +    if path.startswith("/") and (not output or output[0]):
    +        output.insert(0, "")
    +
    +    # If the path starts with '/.' or '/..' ensure we add one more empty
    +    # string to add a trailing '/'
    +    if path.endswith(("/.", "/..")):
    +        output.append("")
    +
    +    return "/".join(output)
    +
    +
    +def _normalize_host(host, scheme):
    +    if host:
    +        if isinstance(host, six.binary_type):
    +            host = six.ensure_str(host)
    +
    +        if scheme in NORMALIZABLE_SCHEMES:
    +            is_ipv6 = IPV6_ADDRZ_RE.match(host)
    +            if is_ipv6:
    +                match = ZONE_ID_RE.search(host)
    +                if match:
    +                    start, end = match.span(1)
    +                    zone_id = host[start:end]
    +
    +                    if zone_id.startswith("%25") and zone_id != "%25":
    +                        zone_id = zone_id[3:]
    +                    else:
    +                        zone_id = zone_id[1:]
    +                    zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS)
    +                    return host[:start].lower() + zone_id + host[end:]
    +                else:
    +                    return host.lower()
    +            elif not IPV4_RE.match(host):
    +                return six.ensure_str(
    +                    b".".join([_idna_encode(label) for label in host.split(".")])
    +                )
    +    return host
    +
    +
    +def _idna_encode(name):
    +    if name and any([ord(x) > 128 for x in name]):
    +        try:
    +            import idna
    +        except ImportError:
    +            six.raise_from(
    +                LocationParseError("Unable to parse URL without the 'idna' module"),
    +                None,
    +            )
    +        try:
    +            return idna.encode(name.lower(), strict=True, std3_rules=True)
    +        except idna.IDNAError:
    +            six.raise_from(
    +                LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None
    +            )
    +    return name.lower().encode("ascii")
    +
    +
    +def _encode_target(target):
    +    """Percent-encodes a request target so that there are no invalid characters"""
    +    if not target.startswith("/"):
    +        return target
    +
    +    path, query = TARGET_RE.match(target).groups()
    +    target = _encode_invalid_chars(path, PATH_CHARS)
    +    query = _encode_invalid_chars(query, QUERY_CHARS)
    +    if query is not None:
    +        target += "?" + query
    +    return target
    +
    +
    +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.
    +
    +    The parser logic and helper functions are based heavily on
    +    work done in the ``rfc3986`` module.
    +
    +    :param str url: URL to parse into a :class:`.Url` namedtuple.
    +
    +    Partly backwards-compatible with :mod:`urlparse`.
    +
    +    Example::
    +
    +        >>> parse_url('http://google.com/mail/')
    +        Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
    +        >>> parse_url('google.com:80')
    +        Url(scheme=None, host='google.com', port=80, path=None, ...)
    +        >>> parse_url('/foo?bar')
    +        Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
    +    """
    +    if not url:
    +        # Empty
    +        return Url()
    +
    +    source_url = url
    +    if not SCHEME_RE.search(url):
    +        url = "//" + url
    +
    +    try:
    +        scheme, authority, path, query, fragment = URI_RE.match(url).groups()
    +        normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES
    +
    +        if scheme:
    +            scheme = scheme.lower()
    +
    +        if authority:
    +            auth, host, port = SUBAUTHORITY_RE.match(authority).groups()
    +            if auth and normalize_uri:
    +                auth = _encode_invalid_chars(auth, USERINFO_CHARS)
    +            if port == "":
    +                port = None
    +        else:
    +            auth, host, port = None, None, None
    +
    +        if port is not None:
    +            port = int(port)
    +            if not (0 <= port <= 65535):
    +                raise LocationParseError(url)
    +
    +        host = _normalize_host(host, scheme)
    +
    +        if normalize_uri and path:
    +            path = _remove_path_dot_segments(path)
    +            path = _encode_invalid_chars(path, PATH_CHARS)
    +        if normalize_uri and query:
    +            query = _encode_invalid_chars(query, QUERY_CHARS)
    +        if normalize_uri and fragment:
    +            fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS)
    +
    +    except (ValueError, AttributeError):
    +        return six.raise_from(LocationParseError(source_url), None)
    +
    +    # For the sake of backwards compatibility we put empty
    +    # string values for path if there are any defined values
    +    # beyond the path in the URL.
    +    # TODO: Remove this when we break backwards compatibility.
    +    if not path:
    +        if query is not None or fragment is not None:
    +            path = ""
    +        else:
    +            path = None
    +
    +    # Ensure that each part of the URL is a `str` for
    +    # backwards compatibility.
    +    if isinstance(url, six.text_type):
    +        ensure_func = six.ensure_text
    +    else:
    +        ensure_func = six.ensure_str
    +
    +    def ensure_type(x):
    +        return x if x is None else ensure_func(x)
    +
    +    return Url(
    +        scheme=ensure_type(scheme),
    +        auth=ensure_type(auth),
    +        host=ensure_type(host),
    +        port=port,
    +        path=ensure_type(path),
    +        query=ensure_type(query),
    +        fragment=ensure_type(fragment),
    +    )
    +
    +
    +def get_host(url):
    +    """
    +    Deprecated. Use :func:`parse_url` instead.
    +    """
    +    p = parse_url(url)
    +    return p.scheme or "http", p.hostname, p.port
    diff --git a/venv/lib/python3.7/site-packages/urllib3/util/wait.py b/venv/lib/python3.7/site-packages/urllib3/util/wait.py
    new file mode 100644
    index 0000000..d71d2fd
    --- /dev/null
    +++ b/venv/lib/python3.7/site-packages/urllib3/util/wait.py
    @@ -0,0 +1,153 @@
    +import errno
    +from functools import partial
    +import select
    +import sys
    +
    +try:
    +    from time import monotonic
    +except ImportError:
    +    from time import time as monotonic
    +
    +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"]
    +
    +
    +class NoWayToWaitForSocketError(Exception):
    +    pass
    +
    +
    +# How should we wait on sockets?
    +#
    +# There are two types of APIs you can use for waiting on sockets: the fancy
    +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like
    +# select/poll. The stateful APIs are more efficient when you have a lots of
    +# sockets to keep track of, because you can set them up once and then use them
    +# lots of times. But we only ever want to wait on a single socket at a time
    +# and don't want to keep track of state, so the stateless APIs are actually
    +# more efficient. So we want to use select() or poll().
    +#
    +# Now, how do we choose between select() and poll()? On traditional Unixes,
    +# select() has a strange calling convention that makes it slow, or fail
    +# altogether, for high-numbered file descriptors. The point of poll() is to fix
    +# that, so on Unixes, we prefer poll().
    +#
    +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper
    +# for it), but that's OK, because on Windows, select() doesn't have this
    +# strange calling convention; plain select() works fine.
    +#
    +# So: on Windows we use select(), and everywhere else we use poll(). We also
    +# fall back to select() in case poll() is somehow broken or missing.
    +
    +if sys.version_info >= (3, 5):
    +    # Modern Python, that retries syscalls by default
    +    def _retry_on_intr(fn, timeout):
    +        return fn(timeout)
    +
    +
    +else:
    +    # Old and broken Pythons.
    +    def _retry_on_intr(fn, timeout):
    +        if timeout is None:
    +            deadline = float("inf")
    +        else:
    +            deadline = monotonic() + timeout
    +
    +        while True:
    +            try:
    +                return fn(timeout)
    +            # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7
    +            except (OSError, select.error) as e:
    +                # 'e.args[0]' incantation works for both OSError and select.error
    +                if e.args[0] != errno.EINTR:
    +                    raise
    +                else:
    +                    timeout = deadline - monotonic()
    +                    if timeout < 0:
    +                        timeout = 0
    +                    if timeout == float("inf"):
    +                        timeout = None
    +                    continue
    +
    +
    +def select_wait_for_socket(sock, read=False, write=False, timeout=None):
    +    if not read and not write:
    +        raise RuntimeError("must specify at least one of read=True, write=True")
    +    rcheck = []
    +    wcheck = []
    +    if read:
    +        rcheck.append(sock)
    +    if write:
    +        wcheck.append(sock)
    +    # When doing a non-blocking connect, most systems signal success by
    +    # marking the socket writable. Windows, though, signals success by marked
    +    # it as "exceptional". We paper over the difference by checking the write
    +    # sockets for both conditions. (The stdlib selectors module does the same
    +    # thing.)
    +    fn = partial(select.select, rcheck, wcheck, wcheck)
    +    rready, wready, xready = _retry_on_intr(fn, timeout)
    +    return bool(rready or wready or xready)
    +
    +
    +def poll_wait_for_socket(sock, read=False, write=False, timeout=None):
    +    if not read and not write:
    +        raise RuntimeError("must specify at least one of read=True, write=True")
    +    mask = 0
    +    if read:
    +        mask |= select.POLLIN
    +    if write:
    +        mask |= select.POLLOUT
    +    poll_obj = select.poll()
    +    poll_obj.register(sock, mask)
    +
    +    # For some reason, poll() takes timeout in milliseconds
    +    def do_poll(t):
    +        if t is not None:
    +            t *= 1000
    +        return poll_obj.poll(t)
    +
    +    return bool(_retry_on_intr(do_poll, timeout))
    +
    +
    +def null_wait_for_socket(*args, **kwargs):
    +    raise NoWayToWaitForSocketError("no select-equivalent available")
    +
    +
    +def _have_working_poll():
    +    # Apparently some systems have a select.poll that fails as soon as you try
    +    # to use it, either due to strange configuration or broken monkeypatching
    +    # from libraries like eventlet/greenlet.
    +    try:
    +        poll_obj = select.poll()
    +        _retry_on_intr(poll_obj.poll, 0)
    +    except (AttributeError, OSError):
    +        return False
    +    else:
    +        return True
    +
    +
    +def wait_for_socket(*args, **kwargs):
    +    # We delay choosing which implementation to use until the first time we're
    +    # called. We could do it at import time, but then we might make the wrong
    +    # decision if someone goes wild with monkeypatching select.poll after
    +    # we're imported.
    +    global wait_for_socket
    +    if _have_working_poll():
    +        wait_for_socket = poll_wait_for_socket
    +    elif hasattr(select, "select"):
    +        wait_for_socket = select_wait_for_socket
    +    else:  # Platform-specific: Appengine.
    +        wait_for_socket = null_wait_for_socket
    +    return wait_for_socket(*args, **kwargs)
    +
    +
    +def wait_for_read(sock, timeout=None):
    +    """ Waits for reading to be available on a given socket.
    +    Returns True if the socket is readable, or False if the timeout expired.
    +    """
    +    return wait_for_socket(sock, read=True, timeout=timeout)
    +
    +
    +def wait_for_write(sock, timeout=None):
    +    """ Waits for writing to be available on a given socket.
    +    Returns True if the socket is readable, or False if the timeout expired.
    +    """
    +    return wait_for_socket(sock, write=True, timeout=timeout)
    diff --git a/venv/lib64 b/venv/lib64
    new file mode 120000
    index 0000000..7951405
    --- /dev/null
    +++ b/venv/lib64
    @@ -0,0 +1 @@
    +lib
    \ No newline at end of file
    diff --git a/venv/pyvenv.cfg b/venv/pyvenv.cfg
    new file mode 100644
    index 0000000..bafe8a4
    --- /dev/null
    +++ b/venv/pyvenv.cfg
    @@ -0,0 +1,3 @@
    +home = /usr/bin
    +include-system-site-packages = false
    +version = 3.7.5