From b96310f62185709973fcbe9ba80771ce2bd54626 Mon Sep 17 00:00:00 2001 From: nathan wagner Date: Sun, 24 Apr 2022 12:44:08 -0400 Subject: [PATCH] added in micropython external library for const feature. it's returing valid attribute arrays, that just need converted on z2m side. it's also respecting the stop command from the console, which I will attempt to expose as just a button press. --- .idea/garageDoorZigbee.iml | 6 + .idea/libraries/MicroPython.xml | 11 + barrier.py | 19 +- main.py | 47 +- .../__pycache__/docopt.cpython-39.pyc | Bin 0 -> 20817 bytes .../__pycache__/uflash.cpython-39.pyc | Bin 0 -> 650011 bytes .../adafruit_ampy-1.0.7.dist-info/INSTALLER | 1 + .../adafruit_ampy-1.0.7.dist-info/LICENSE | 21 + .../adafruit_ampy-1.0.7.dist-info/METADATA | 147 + .../adafruit_ampy-1.0.7.dist-info/RECORD | 17 + .../adafruit_ampy-1.0.7.dist-info/REQUESTED | 0 .../adafruit_ampy-1.0.7.dist-info/WHEEL | 6 + .../entry_points.txt | 3 + .../top_level.txt | 1 + venv/Lib/site-packages/ampy/__init__.py | 0 .../ampy/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 172 bytes .../ampy/__pycache__/cli.cpython-39.pyc | Bin 0 -> 11428 bytes .../ampy/__pycache__/files.cpython-39.pyc | Bin 0 -> 8646 bytes .../ampy/__pycache__/pyboard.cpython-39.pyc | Bin 0 -> 9714 bytes venv/Lib/site-packages/ampy/cli.py | 429 + venv/Lib/site-packages/ampy/files.py | 310 + venv/Lib/site-packages/ampy/pyboard.py | 343 + .../click-8.1.2.dist-info/INSTALLER | 1 + .../click-8.1.2.dist-info/LICENSE.rst | 28 + .../click-8.1.2.dist-info/METADATA | 111 + .../click-8.1.2.dist-info/RECORD | 39 + .../site-packages/click-8.1.2.dist-info/WHEEL | 5 + .../click-8.1.2.dist-info/top_level.txt | 1 + venv/Lib/site-packages/click/__init__.py | 73 + .../click/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 2617 bytes .../click/__pycache__/_compat.cpython-39.pyc | Bin 0 -> 16056 bytes .../__pycache__/_termui_impl.cpython-39.pyc | Bin 0 -> 16003 bytes .../__pycache__/_textwrap.cpython-39.pyc | Bin 0 -> 1533 bytes .../__pycache__/_winconsole.cpython-39.pyc | Bin 0 -> 7784 bytes .../click/__pycache__/core.cpython-39.pyc | Bin 0 -> 89577 bytes .../__pycache__/decorators.cpython-39.pyc | Bin 0 -> 15541 bytes .../__pycache__/exceptions.cpython-39.pyc | Bin 0 -> 10132 bytes .../__pycache__/formatting.cpython-39.pyc | Bin 0 -> 9412 bytes .../click/__pycache__/globals.cpython-39.pyc | Bin 0 -> 2424 bytes .../click/__pycache__/parser.cpython-39.pyc | Bin 0 -> 13567 bytes .../shell_completion.cpython-39.pyc | Bin 0 -> 16728 bytes .../click/__pycache__/termui.cpython-39.pyc | Bin 0 -> 25946 bytes .../click/__pycache__/testing.cpython-39.pyc | Bin 0 -> 15138 bytes .../click/__pycache__/types.cpython-39.pyc | Bin 0 -> 32955 bytes .../click/__pycache__/utils.cpython-39.pyc | Bin 0 -> 17574 bytes venv/Lib/site-packages/click/_compat.py | 626 + venv/Lib/site-packages/click/_termui_impl.py | 717 + venv/Lib/site-packages/click/_textwrap.py | 49 + venv/Lib/site-packages/click/_winconsole.py | 279 + venv/Lib/site-packages/click/core.py | 2995 ++++ venv/Lib/site-packages/click/decorators.py | 497 + venv/Lib/site-packages/click/exceptions.py | 287 + venv/Lib/site-packages/click/formatting.py | 301 + venv/Lib/site-packages/click/globals.py | 68 + venv/Lib/site-packages/click/parser.py | 529 + venv/Lib/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 580 + venv/Lib/site-packages/click/termui.py | 787 + venv/Lib/site-packages/click/testing.py | 479 + venv/Lib/site-packages/click/types.py | 1073 ++ venv/Lib/site-packages/click/utils.py | 580 + .../colorama-0.4.4.dist-info/INSTALLER | 1 + .../colorama-0.4.4.dist-info/LICENSE.txt | 27 + .../colorama-0.4.4.dist-info/METADATA | 415 + .../colorama-0.4.4.dist-info/RECORD | 18 + .../colorama-0.4.4.dist-info/WHEEL | 6 + .../colorama-0.4.4.dist-info/top_level.txt | 1 + venv/Lib/site-packages/colorama/__init__.py | 6 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 427 bytes .../colorama/__pycache__/ansi.cpython-39.pyc | Bin 0 -> 3212 bytes .../__pycache__/ansitowin32.cpython-39.pyc | Bin 0 -> 7678 bytes .../__pycache__/initialise.cpython-39.pyc | Bin 0 -> 1694 bytes .../colorama/__pycache__/win32.cpython-39.pyc | Bin 0 -> 3926 bytes .../__pycache__/winterm.cpython-39.pyc | Bin 0 -> 4648 bytes venv/Lib/site-packages/colorama/ansi.py | 102 + .../Lib/site-packages/colorama/ansitowin32.py | 258 + venv/Lib/site-packages/colorama/initialise.py | 80 + venv/Lib/site-packages/colorama/win32.py | 152 + venv/Lib/site-packages/colorama/winterm.py | 169 + .../docopt-0.6.2.dist-info/INSTALLER | 1 + .../docopt-0.6.2.dist-info/LICENSE-MIT | 19 + .../docopt-0.6.2.dist-info/METADATA | 470 + .../docopt-0.6.2.dist-info/RECORD | 9 + .../docopt-0.6.2.dist-info/REQUESTED | 0 .../docopt-0.6.2.dist-info/WHEEL | 6 + .../docopt-0.6.2.dist-info/top_level.txt | 1 + venv/Lib/site-packages/docopt.py | 579 + venv/Lib/site-packages/dotenv/__init__.py | 49 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1231 bytes .../dotenv/__pycache__/cli.cpython-39.pyc | Bin 0 -> 4821 bytes .../dotenv/__pycache__/ipython.cpython-39.pyc | Bin 0 -> 1477 bytes .../dotenv/__pycache__/main.cpython-39.pyc | Bin 0 -> 9841 bytes .../dotenv/__pycache__/parser.cpython-39.pyc | Bin 0 -> 5876 bytes .../__pycache__/variables.cpython-39.pyc | Bin 0 -> 3513 bytes .../dotenv/__pycache__/version.cpython-39.pyc | Bin 0 -> 195 bytes venv/Lib/site-packages/dotenv/cli.py | 164 + venv/Lib/site-packages/dotenv/ipython.py | 39 + venv/Lib/site-packages/dotenv/main.py | 373 + venv/Lib/site-packages/dotenv/parser.py | 182 + venv/Lib/site-packages/dotenv/py.typed | 1 + venv/Lib/site-packages/dotenv/variables.py | 88 + venv/Lib/site-packages/dotenv/version.py | 1 + .../pyserial-3.5.dist-info/DESCRIPTION.rst | 13 + .../pyserial-3.5.dist-info/INSTALLER | 1 + .../pyserial-3.5.dist-info/METADATA | 47 + .../pyserial-3.5.dist-info/RECORD | 67 + .../pyserial-3.5.dist-info/REQUESTED | 0 .../pyserial-3.5.dist-info/WHEEL | 6 + .../pyserial-3.5.dist-info/entry_points.txt | 4 + .../pyserial-3.5.dist-info/metadata.json | 1 + .../pyserial-3.5.dist-info/top_level.txt | 1 + .../python_dotenv-0.20.0.dist-info/INSTALLER | 1 + .../python_dotenv-0.20.0.dist-info/LICENSE | 87 + .../python_dotenv-0.20.0.dist-info/METADATA | 594 + .../python_dotenv-0.20.0.dist-info/RECORD | 23 + .../python_dotenv-0.20.0.dist-info/WHEEL | 5 + .../entry_points.txt | 3 + .../top_level.txt | 1 + venv/Lib/site-packages/serial/__init__.py | 91 + venv/Lib/site-packages/serial/__main__.py | 3 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 2168 bytes .../__pycache__/__main__.cpython-39.pyc | Bin 0 -> 228 bytes .../serial/__pycache__/rfc2217.cpython-39.pyc | Bin 0 -> 32560 bytes .../serial/__pycache__/rs485.cpython-39.pyc | Bin 0 -> 2936 bytes .../__pycache__/serialcli.cpython-39.pyc | Bin 0 -> 6732 bytes .../__pycache__/serialjava.cpython-39.pyc | Bin 0 -> 7445 bytes .../__pycache__/serialposix.cpython-39.pyc | Bin 0 -> 21827 bytes .../__pycache__/serialutil.cpython-39.pyc | Bin 0 -> 18642 bytes .../__pycache__/serialwin32.cpython-39.pyc | Bin 0 -> 13113 bytes .../serial/__pycache__/win32.cpython-39.pyc | Bin 0 -> 6410 bytes venv/Lib/site-packages/serial/rfc2217.py | 1351 ++ venv/Lib/site-packages/serial/rs485.py | 94 + venv/Lib/site-packages/serial/serialcli.py | 253 + venv/Lib/site-packages/serial/serialjava.py | 251 + venv/Lib/site-packages/serial/serialposix.py | 900 + venv/Lib/site-packages/serial/serialutil.py | 697 + venv/Lib/site-packages/serial/serialwin32.py | 477 + .../site-packages/serial/threaded/__init__.py | 297 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 9544 bytes .../site-packages/serial/tools/__init__.py | 0 .../tools/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 180 bytes .../__pycache__/hexlify_codec.cpython-39.pyc | Bin 0 -> 4850 bytes .../__pycache__/list_ports.cpython-39.pyc | Bin 0 -> 2546 bytes .../list_ports_common.cpython-39.pyc | Bin 0 -> 3564 bytes .../list_ports_linux.cpython-39.pyc | Bin 0 -> 3038 bytes .../__pycache__/list_ports_osx.cpython-39.pyc | Bin 0 -> 6358 bytes .../list_ports_posix.cpython-39.pyc | Bin 0 -> 3929 bytes .../list_ports_windows.cpython-39.pyc | Bin 0 -> 7637 bytes .../tools/__pycache__/miniterm.cpython-39.pyc | Bin 0 -> 29322 bytes .../serial/tools/hexlify_codec.py | 126 + .../site-packages/serial/tools/list_ports.py | 110 + .../serial/tools/list_ports_common.py | 121 + .../serial/tools/list_ports_linux.py | 109 + .../serial/tools/list_ports_osx.py | 299 + .../serial/tools/list_ports_posix.py | 119 + .../serial/tools/list_ports_windows.py | 427 + .../site-packages/serial/tools/miniterm.py | 1042 ++ .../serial/urlhandler/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 185 bytes .../__pycache__/protocol_alt.cpython-39.pyc | Bin 0 -> 1407 bytes .../protocol_cp2110.cpython-39.pyc | Bin 0 -> 5728 bytes .../protocol_hwgrep.cpython-39.pyc | Bin 0 -> 1869 bytes .../__pycache__/protocol_loop.cpython-39.pyc | Bin 0 -> 8308 bytes .../protocol_rfc2217.cpython-39.pyc | Bin 0 -> 288 bytes .../protocol_socket.cpython-39.pyc | Bin 0 -> 9044 bytes .../__pycache__/protocol_spy.cpython-39.pyc | Bin 0 -> 9146 bytes .../serial/urlhandler/protocol_alt.py | 57 + .../serial/urlhandler/protocol_cp2110.py | 258 + .../serial/urlhandler/protocol_hwgrep.py | 91 + .../serial/urlhandler/protocol_loop.py | 308 + .../serial/urlhandler/protocol_rfc2217.py | 12 + .../serial/urlhandler/protocol_socket.py | 359 + .../serial/urlhandler/protocol_spy.py | 290 + venv/Lib/site-packages/serial/win32.py | 366 + .../uflash-1.2.4.dist-info/INSTALLER | 1 + .../uflash-1.2.4.dist-info/LICENSE | 18 + .../uflash-1.2.4.dist-info/METADATA | 329 + .../uflash-1.2.4.dist-info/RECORD | 11 + .../uflash-1.2.4.dist-info/REQUESTED | 0 .../uflash-1.2.4.dist-info/WHEEL | 5 + .../uflash-1.2.4.dist-info/entry_points.txt | 3 + .../uflash-1.2.4.dist-info/top_level.txt | 1 + venv/Lib/site-packages/uflash.py | 14945 ++++++++++++++++ venv/Scripts/ampy.exe | Bin 0 -> 106360 bytes venv/Scripts/dotenv.exe | Bin 0 -> 106362 bytes venv/Scripts/pyserial-miniterm.exe | Bin 0 -> 106375 bytes venv/Scripts/pyserial-ports.exe | Bin 0 -> 106377 bytes venv/Scripts/uflash.exe | Bin 0 -> 106360 bytes 188 files changed, 39329 insertions(+), 29 deletions(-) create mode 100644 .idea/libraries/MicroPython.xml create mode 100644 venv/Lib/site-packages/__pycache__/docopt.cpython-39.pyc create mode 100644 venv/Lib/site-packages/__pycache__/uflash.cpython-39.pyc create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/LICENSE create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/METADATA create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/RECORD create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/ampy/__init__.py create mode 100644 venv/Lib/site-packages/ampy/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/ampy/__pycache__/cli.cpython-39.pyc create mode 100644 venv/Lib/site-packages/ampy/__pycache__/files.cpython-39.pyc create mode 100644 venv/Lib/site-packages/ampy/__pycache__/pyboard.cpython-39.pyc create mode 100644 venv/Lib/site-packages/ampy/cli.py create mode 100644 venv/Lib/site-packages/ampy/files.py create mode 100644 venv/Lib/site-packages/ampy/pyboard.py create mode 100644 venv/Lib/site-packages/click-8.1.2.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/click-8.1.2.dist-info/LICENSE.rst create mode 100644 venv/Lib/site-packages/click-8.1.2.dist-info/METADATA create mode 100644 venv/Lib/site-packages/click-8.1.2.dist-info/RECORD create mode 100644 venv/Lib/site-packages/click-8.1.2.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/click-8.1.2.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/click/__init__.py create mode 100644 venv/Lib/site-packages/click/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/_compat.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/core.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/decorators.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/exceptions.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/formatting.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/globals.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/parser.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/termui.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/testing.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/types.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/__pycache__/utils.cpython-39.pyc create mode 100644 venv/Lib/site-packages/click/_compat.py create mode 100644 venv/Lib/site-packages/click/_termui_impl.py create mode 100644 venv/Lib/site-packages/click/_textwrap.py create mode 100644 venv/Lib/site-packages/click/_winconsole.py create mode 100644 venv/Lib/site-packages/click/core.py create mode 100644 venv/Lib/site-packages/click/decorators.py create mode 100644 venv/Lib/site-packages/click/exceptions.py create mode 100644 venv/Lib/site-packages/click/formatting.py create mode 100644 venv/Lib/site-packages/click/globals.py create mode 100644 venv/Lib/site-packages/click/parser.py create mode 100644 venv/Lib/site-packages/click/py.typed create mode 100644 venv/Lib/site-packages/click/shell_completion.py create mode 100644 venv/Lib/site-packages/click/termui.py create mode 100644 venv/Lib/site-packages/click/testing.py create mode 100644 venv/Lib/site-packages/click/types.py create mode 100644 venv/Lib/site-packages/click/utils.py create mode 100644 venv/Lib/site-packages/colorama-0.4.4.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/colorama-0.4.4.dist-info/LICENSE.txt create mode 100644 venv/Lib/site-packages/colorama-0.4.4.dist-info/METADATA create mode 100644 venv/Lib/site-packages/colorama-0.4.4.dist-info/RECORD create mode 100644 venv/Lib/site-packages/colorama-0.4.4.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/colorama-0.4.4.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/colorama/__init__.py create mode 100644 venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-39.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-39.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-39.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/win32.cpython-39.pyc create mode 100644 venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-39.pyc create mode 100644 venv/Lib/site-packages/colorama/ansi.py create mode 100644 venv/Lib/site-packages/colorama/ansitowin32.py create mode 100644 venv/Lib/site-packages/colorama/initialise.py create mode 100644 venv/Lib/site-packages/colorama/win32.py create mode 100644 venv/Lib/site-packages/colorama/winterm.py create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/METADATA create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/RECORD create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/docopt-0.6.2.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/docopt.py create mode 100644 venv/Lib/site-packages/dotenv/__init__.py create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/main.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/__pycache__/version.cpython-39.pyc create mode 100644 venv/Lib/site-packages/dotenv/cli.py create mode 100644 venv/Lib/site-packages/dotenv/ipython.py create mode 100644 venv/Lib/site-packages/dotenv/main.py create mode 100644 venv/Lib/site-packages/dotenv/parser.py create mode 100644 venv/Lib/site-packages/dotenv/py.typed create mode 100644 venv/Lib/site-packages/dotenv/variables.py create mode 100644 venv/Lib/site-packages/dotenv/version.py create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/DESCRIPTION.rst create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/METADATA create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/RECORD create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/metadata.json create mode 100644 venv/Lib/site-packages/pyserial-3.5.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/LICENSE create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/serial/__init__.py create mode 100644 venv/Lib/site-packages/serial/__main__.py create mode 100644 venv/Lib/site-packages/serial/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/__main__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/rfc2217.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/rs485.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/serialcli.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/serialjava.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/serialposix.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/serialutil.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/serialwin32.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/__pycache__/win32.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/rfc2217.py create mode 100644 venv/Lib/site-packages/serial/rs485.py create mode 100644 venv/Lib/site-packages/serial/serialcli.py create mode 100644 venv/Lib/site-packages/serial/serialjava.py create mode 100644 venv/Lib/site-packages/serial/serialposix.py create mode 100644 venv/Lib/site-packages/serial/serialutil.py create mode 100644 venv/Lib/site-packages/serial/serialwin32.py create mode 100644 venv/Lib/site-packages/serial/threaded/__init__.py create mode 100644 venv/Lib/site-packages/serial/threaded/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__init__.py create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/hexlify_codec.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/list_ports.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/list_ports_common.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/list_ports_linux.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/list_ports_osx.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/list_ports_posix.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/list_ports_windows.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/__pycache__/miniterm.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/tools/hexlify_codec.py create mode 100644 venv/Lib/site-packages/serial/tools/list_ports.py create mode 100644 venv/Lib/site-packages/serial/tools/list_ports_common.py create mode 100644 venv/Lib/site-packages/serial/tools/list_ports_linux.py create mode 100644 venv/Lib/site-packages/serial/tools/list_ports_osx.py create mode 100644 venv/Lib/site-packages/serial/tools/list_ports_posix.py create mode 100644 venv/Lib/site-packages/serial/tools/list_ports_windows.py create mode 100644 venv/Lib/site-packages/serial/tools/miniterm.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/__init__.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/__init__.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_alt.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_cp2110.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_hwgrep.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_loop.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_rfc2217.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_socket.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_spy.cpython-39.pyc create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_alt.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_cp2110.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_hwgrep.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_loop.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_rfc2217.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_socket.py create mode 100644 venv/Lib/site-packages/serial/urlhandler/protocol_spy.py create mode 100644 venv/Lib/site-packages/serial/win32.py create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/LICENSE create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/METADATA create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/RECORD create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/uflash-1.2.4.dist-info/top_level.txt create mode 100644 venv/Lib/site-packages/uflash.py create mode 100644 venv/Scripts/ampy.exe create mode 100644 venv/Scripts/dotenv.exe create mode 100644 venv/Scripts/pyserial-miniterm.exe create mode 100644 venv/Scripts/pyserial-ports.exe create mode 100644 venv/Scripts/uflash.exe diff --git a/.idea/garageDoorZigbee.iml b/.idea/garageDoorZigbee.iml index fe98e73..46ad9b0 100644 --- a/.idea/garageDoorZigbee.iml +++ b/.idea/garageDoorZigbee.iml @@ -4,6 +4,11 @@ + + + + + @@ -13,5 +18,6 @@ + \ No newline at end of file diff --git a/.idea/libraries/MicroPython.xml b/.idea/libraries/MicroPython.xml new file mode 100644 index 0000000..d17d63f --- /dev/null +++ b/.idea/libraries/MicroPython.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/barrier.py b/barrier.py index 5677664..fbe7bd1 100644 --- a/barrier.py +++ b/barrier.py @@ -3,7 +3,7 @@ import time import com import xbee from machine import Pin - +from micropython import const class Barrier: moving = b'\x00' @@ -14,6 +14,10 @@ class Barrier: door = ad0.value().to_bytes(2,"big") motor = ad1.value().to_bytes(2, "big") update = True + duint8 = const(0x20) + _duint16 = const(0x21) + denum8 = const(0x30) + _dbool = const(0x10) def status(self,seq, kwargs): #moving state is x0001 enum8(0x30) @@ -21,21 +25,14 @@ class Barrier: # 2 octets attribute identifier # 1 octet attribute data type # 1 octet attribute value - print("really messed up early in the game eh") attributes = kwargs['attributes'] - print(attributes) if len(attributes) == 1: - print("length") if attributes[0] == 10: - print("position request") - print(self.barrier_position) - return self.barrier_position + return bytes([0,10]) + bytes([self.duint8]) + self.barrier_position if attributes[0] == 1: - print("moving request") - print(self.moving) - return self.moving + return bytes([0,0]) + bytes([self.denum8]) + self.moving #to_return = bytes([1,0,48]) + bytes(self.moving) + bytes([10,0,20]) + bytes(self.barrier_position) - return -1 + return b'\xFFFF' def command(self, seq, payload): diff --git a/main.py b/main.py index 0ed2a96..5d55c3a 100644 --- a/main.py +++ b/main.py @@ -4,17 +4,16 @@ import time import xbee import spec -import ubinascii -from machine import I2C import barrier from machine import Pin import gen import com +from micropython import const import struct #ad0 = Pin("D0", Pin.IN, Pin.PULL_UP) #ad1 = Pin("D1", Pin.IN, Pin.PULL_UP) -#ad2 = Pin("D2", Pin.IN, Pin.PULL_UP) +#ad2 = Pin("D2", Pin.IN, Pin.PULL_UP)help while xbee.atcmd("AI") != 0: time.sleep(0.1) ad4 = Pin("D4", Pin.OUT, value=0) @@ -25,6 +24,7 @@ xbee.modem_status.callback(status_cb) # arduino_addr = 0x48 # senddata = 0 key = b'\x5A\x69\x67\x42\x65\x65\x41\x6C\x6C\x69\x61\x6E\x63\x65\x30\x39' +#_key = const(0x5A 0x69 0x67 0x42 0x65 0x65 0x41 0x6C 0x6C 0x69 0x61 0x6E 0x63 0x65 0x30 0x39) xbee.atcmd('KY', key) @@ -40,7 +40,17 @@ diff = 3600000 first_report = False timestamp = time.ticks_ms() garage = barrier.Barrier() - +payload_header=b'\x0c\x1e\x10' +_rap=const(0x01) #read attribute response +_dap=const(0x0d) #discover attribute response +_war=const(0x05) #write attribute no response +_ra =const(0x0a) #report attributes +oob=b'\xab' #out of band sequence number +duint8 = b'\x20' +duint16 =b'\x21' +denum8 = b'\x30' +dbool = b'\x10' +SUCCESS = b'\x00' while 1 != 0: packet = com.receive() if packet is not None: @@ -50,17 +60,19 @@ while 1 != 0: if packet['cluster'] == 259: #barrier cluster cluster_name, seq, CommandType, command_name, disable_default_response, kwargs = spec.decode_zcl( packet['cluster'], packet['payload']) + print("printing kwargs for incoming packet") + print(command_name) if "attributes" in kwargs: #garage.status(seq,packet['payload']) print("found attribute request") stat=garage.status(seq, kwargs) - if payload != -1: + if stat != 'b\xffff': print("garage status") + payload = payload_header+ bytes([seq])+ bytes([_rap]) + stat print(payload) - payload = bytes([12, 30, 16, seq, 1]) - payload = payload + bytes([0, 0, 16,stat ]) com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'], cluster=packet['cluster'], profile=packet['profile']) - if CommandType is not None: + #if CommandType is not None: + if command_name == "stop": garage.command(seq, packet['payload']) pass if packet['cluster'] == 6: #genOnOffCluster in HA Profile @@ -72,17 +84,14 @@ while 1 != 0: # print(kwargs) if "attributes" in kwargs: if kwargs['attributes'][0] == 0: - payload = bytes([12, 30, 16, seq, 1]) #zcl_header - payload = payload + bytes([0, 0, 0,16, ad4.value()]) - # payload= attr_bytes + payload = payload_header + bytes([seq]) + bytes([_rap]) + bytes([0,0])+ SUCCESS + dbool + bytes([ad4.value()]) print(payload) com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'], cluster=packet['cluster'], profile=packet['profile']) if kwargs['attributes'][0] == 10: - payload = bytes([12, 30, 16, seq, 1]) - payload = payload + bytes([0, 0, 16, ad4.value()]) + payload = payload_header + bytes([seq]) + _rap + bytes([0, 0, 16, ad4.value()]) # payload= attr_bytes - #print(payload) + print(payload) com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'], cluster=packet['cluster'], profile=packet['profile']) if command_name == "on": @@ -122,8 +131,8 @@ while 1 != 0: attr_bytes=gen.attribute_result(kwargs) #payload: control byte, code bytes(2), seq copy, command identifier(read_attributes_response, #payload = bytes([4, 30, 16, seq, 1, attr_bytes, 0, 8, 0]) - payload = bytes([12, 30, 16, seq, 1]) - payload = payload+attr_bytes + zcl_header = payload_header + bytes([seq]) + _rap + payload = zcl_header+attr_bytes #payload= attr_bytes print(payload) com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'], cluster=packet['cluster'], profile=packet['profile']) @@ -154,9 +163,11 @@ while 1 != 0: #payload = zcl_head# + payload garage.watch() florp = garage.barrier_position - dumb = bytes([12, 30, 16, 171, 5]) + zcl_header = payload_header +oob + bytes([_ra]) + payload=zcl_header+bytes([5]) + #dumb = bytes([12, 30, 16, 171, 5]) com.fancy_transmit(payload=bytes([12, 30, 16, 171, 10])+florp, source_ep=8, dest_ep=1, cluster=6, profile=260) - com.fancy_transmit(payload=dumb , source_ep=8, dest_ep=1, cluster=259, profile=260) + com.fancy_transmit(payload=payload , source_ep=8, dest_ep=1, cluster=259, profile=260) if garage.watch(): zcl_head = bytes([12, 30, 16, 171, 10]) # zcl_header payl = zcl_head + garage.status() diff --git a/venv/Lib/site-packages/__pycache__/docopt.cpython-39.pyc b/venv/Lib/site-packages/__pycache__/docopt.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00974ede60a3bced7cbd97ad777f4d56246234a2 GIT binary patch literal 20817 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 X`A73Z`H_4vf3k2qU%+1`kJSGMtTh@8 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/__pycache__/uflash.cpython-39.pyc b/venv/Lib/site-packages/__pycache__/uflash.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b837885d5159f39e807164633ed10673cc760ca GIT binary patch literal 650011 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/INSTALLER b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/LICENSE b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/LICENSE new file mode 100644 index 0000000..19a9cd8 --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Adafruit Industries + +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/site-packages/adafruit_ampy-1.0.7.dist-info/METADATA b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/METADATA new file mode 100644 index 0000000..8f7ac05 --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/METADATA @@ -0,0 +1,147 @@ +Metadata-Version: 2.1 +Name: adafruit-ampy +Version: 1.0.7 +Summary: ampy (Adafruit MicroPython tool) is a command line tool to interact with a CircuitPython or MicroPython board over a serial connection. +Home-page: https://github.com/adafruit/ampy +Author: Adafruit Industries +Author-email: circuitpython@adafruit.com +License: MIT +Keywords: adafruit ampy hardware micropython circuitpython +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +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 +Description-Content-Type: text/markdown +Requires-Dist: click +Requires-Dist: pyserial +Requires-Dist: python-dotenv + +# ampy +Adafruit MicroPython Tool (ampy) - Utility to interact with a CircuitPython or MicroPython board over a serial connection. + +Ampy is meant to be a simple command line tool to manipulate files and run code on a CircuitPython or +MicroPython board over its serial connection. +With ampy you can send files from your computer to the +board's file system, download files from a board to your computer, and even send a Python script +to a board to be executed. + +Note that ampy by design is meant to be simple and does not support advanced interaction like a shell +or terminal to send input to a board. Check out other MicroPython tools +like [rshell](https://github.com/dhylands/rshell) +or [mpfshell](https://github.com/wendlers/mpfshell) for more advanced interaction with boards. + +## Installation + +You can use ampy with either Python 2.7.x or 3.x and can install it easily from +Python's package index. On MacOS or Linux, in a terminal run the following command (assuming +Python 3): + + pip3 install --user adafruit-ampy + +On Windows, do: + + pip install adafruit-ampy + +Note on some Linux and Mac OSX systems you might need to run as root with sudo: + + sudo pip3 install adafruit-ampy + +If you don't have Python 3 then try using Python 2 with: + + pip install adafruit-ampy + +Once installed verify you can run the ampy program and get help output: + + ampy --help + +You should see usage information displayed like below: + + Usage: ampy [OPTIONS] COMMAND [ARGS]... + + ampy - Adafruit MicroPython Tool + + Ampy is a tool to control MicroPython boards over a serial connection. + Using ampy you can manipulate files on the board's internal filesystem and + even run scripts. + + Options: + -p, --port PORT Name of serial port for connected board. [required] + -b, --baud BAUD Baud rate for the serial connection. (default 115200) + --help Show this message and exit. + + Commands: + get Retrieve a file from the board. + ls List contents of a directory on the board. + put Put a file on the board. + rm Remove a file from the board. + run Run a script and print its output. + +If you'd like to install from the Github source then use the standard Python +setup.py install (or develop mode): + + python3 setup.py install + +Note to run the unit tests on Python 2 you must install the mock library: + + pip install mock + +## Usage + +Ampy is made to talk to a CircuitPython MicroPython board over its serial connection. You will +need your board connected and any drivers to access it serial port installed. +Then for example to list the files on the board run a command like: + + ampy --port /dev/tty.SLAB_USBtoUART ls + +You should see a list of files on the board's root directory printed to the +terminal. Note that you'll need to change the port parameter to the name or path +to the serial port that the MicroPython board is connected to. + +Other commands are available, run ampy with --help to see more information: + + ampy --help + +Each subcommand has its own help, for example to see help for the ls command run (note you +unfortunately must have a board connected and serial port specified): + + ampy --port /dev/tty.SLAB_USBtoUART ls --help + +## Configuration + +For convenience you can set an `AMPY_PORT` environment variable which will be used +if the port parameter is not specified. For example on Linux or OSX: + + export AMPY_PORT=/dev/tty.SLAB_USBtoUART + ampy ls + +Or on Windows (untested) try the SET command: + + set AMPY_PORT=COM4 + ampy ls + +Similarly, you can set `AMPY_BAUD` and `AMPY_DELAY` to control your baud rate and +the delay before entering RAW MODE. + +To set these variables automatically each time you run `ampy`, copy them into a +file named `.ampy`: + +```sh +# Example .ampy file +# Please fill in your own port, baud rate, and delay +AMPY_PORT=/dev/cu.wchusbserial1410 +AMPY_BAUD=115200 +# Fix for macOS users' "Could not enter raw repl"; try 2.0 and lower from there: +AMPY_DELAY=0.5 +``` + +You can put the `.ampy` file in your working directory, one of its parents, or in +your home directory. + + diff --git a/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/RECORD b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/RECORD new file mode 100644 index 0000000..0aa4014 --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/RECORD @@ -0,0 +1,17 @@ +../../Scripts/ampy.exe,sha256=yI_2sYdJofDJzqEtwYkOlJKoAQx3PLh9D0cKncTdOF4,106360 +adafruit_ampy-1.0.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +adafruit_ampy-1.0.7.dist-info/LICENSE,sha256=9gQ2ZE4GYwHUwujx8GdKFXsfYOT9VYZxe5nd4lXMGM0,1076 +adafruit_ampy-1.0.7.dist-info/METADATA,sha256=J30k_3TI3-vG8GSll8IWg3yBpCFMuCIr0tvNswYxTf0,5087 +adafruit_ampy-1.0.7.dist-info/RECORD,, +adafruit_ampy-1.0.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +adafruit_ampy-1.0.7.dist-info/WHEEL,sha256=CihQvCnsGZQBGAHLEUMf0IdA4fRduS_NBUTMgCTtvPM,110 +adafruit_ampy-1.0.7.dist-info/entry_points.txt,sha256=zTIIUmlgcc2fs6jESYBEpIg6vF6O0rHJrTPpe0FVhdM,39 +adafruit_ampy-1.0.7.dist-info/top_level.txt,sha256=V20_LZHKhLbhLQGa8h22OpV1VD6ngxxFNEukjkCBn2c,5 +ampy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ampy/__pycache__/__init__.cpython-39.pyc,, +ampy/__pycache__/cli.cpython-39.pyc,, +ampy/__pycache__/files.cpython-39.pyc,, +ampy/__pycache__/pyboard.cpython-39.pyc,, +ampy/cli.py,sha256=SVOtiASrbDeanLyU86kKSwqZ1gPX_-P4w6eOh3TJW_8,14955 +ampy/files.py,sha256=swfqotremCZLeuT_sGqRdJXowB-PHvBou_Ic6JtUn3o,13160 +ampy/pyboard.py,sha256=JmwUfwKMKgnWzn-CNOtb-v0ZQJM5YyHaZHlVebQnBHY,11914 diff --git a/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/REQUESTED b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/WHEEL b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/WHEEL new file mode 100644 index 0000000..dea0e20 --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.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/site-packages/adafruit_ampy-1.0.7.dist-info/entry_points.txt b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/entry_points.txt new file mode 100644 index 0000000..ec3e4ed --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +ampy = ampy.cli:cli + diff --git a/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/top_level.txt b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/top_level.txt new file mode 100644 index 0000000..330cc11 --- /dev/null +++ b/venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/top_level.txt @@ -0,0 +1 @@ +ampy diff --git a/venv/Lib/site-packages/ampy/__init__.py b/venv/Lib/site-packages/ampy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/ampy/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/ampy/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..466289d87d631bf20cefe4fac07256d557020f14 GIT binary patch literal 172 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/ampy/__pycache__/files.cpython-39.pyc b/venv/Lib/site-packages/ampy/__pycache__/files.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ba2aaa941419940d9e384a5f9e8cfe1a3653e53 GIT binary patch literal 8646 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/ampy/__pycache__/pyboard.cpython-39.pyc b/venv/Lib/site-packages/ampy/__pycache__/pyboard.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..408145958e8fd51c5ae6273003bfab7754226ea5 GIT binary patch literal 9714 zcma)CTWlQHd7j(u&JLFpMbWxP9?KVTRg;#S)=_0gmL7Q;P5YN_Sy z^2|^Y*UPju6~s*|)Ngf-KCFN|*g(+&MG>HV42l8;`cUMdK<6Ro<)L6-1N6ZUO~3D- z+2t;!1YKgzoclTRpa1{<%V97%=^6NaFAA4_v|t$j$-?N*Md2cz_{T_;Q8SdOtd?0b zdYN6)5Z$yV3BP?#dW*Yosea-p6h^#Y)vX%3|sFmI}R6!LV7^)b$ z+jgyta!I);yW3XHL#eDhlsuFs!b&)yCc=r;$!*&-)Kh9&?SEj_rqql&fVuXC(`xpP zrTgK&ZLFrOn@06u@DG+@c%99qcA%BtjFM0<2aVA47H>CWf32-HTA|<3?Ym7C#(r`; z^l#*S8?7LYU-UQPQ29yQZ?xCeHlk)DNJ9T!Gr8>tk@6btDADbfj2o=cz8h*k@Z(T7 zgO>l^%}W>>MPVapwxb2lyK+BR>tM}|I9LhKd!CP9bFI_XiJ#9PRml5aAJi=5dzw4- zk_zv>lq8#PT>kc%Q&rEquJb8=MgOzE{qn-uSI#Y*T{z3`i?_pQcfdkOa@2Kjudc&R zYpyz2c_F+XHs(%Y&V{R2-kdvIU1&#hRZPImvNpWmOh(6f*V;*V-fu4BvzpNgXqen? zM=z5uQ9dAS_-h++;xB=Gx|t+s2;yN|eZgC7`+5VFe%#Q_PU0`?_L>m3)oy^0YeD07 zGYb87B$e5D;tX(jI90#Bkx0D`}$V} zVZT6kqw1t3o;^e&Eyjrg8PfvKCrk^=t&RA0)lAK$FAQBq=Pz$uK7Z>yFm`r6c?{AI}Af3t9 ztjm)1rA-jzt5_*6a&uzcd-#3yF2Q}wD$3*}khHFasv7OjxyU0#VhCA7( zEM)_mfO4t;Y;sglmGCYAvxsPBt*9nc1vMo#siyFDW%v8jsl~7rg~?+3rU1ybMgj8< zekEjBmh=7_WB|NI%h)RP3ae%hV65ziIP<#IbC6>3p0R4JI(R#5xohapS<^2h#h#@K z+m?cMSG3(JHTbTSbg2ZewJ9f?StWRAt-z z2)o4*O^xk{U`PVe-~7^z7cj`c`E=M z2-X7-f~8h72FOE1c^o4co}oWAg@|!=GA(C)TFs?eF+07~N}{x)!$8&Zk4rr%ZA3}4 zrODlCLAX72*dZ;%tuXAQ6ODGOm8ptY^V?Eqxru#~6RV}viNn@%YBf8l1GS`6x06#u zA4Ug#kO@caVPpzGGXi4QUqfSj6p3NF=CoNfXU&7wtmR2sL7iiD4~;Y6!eH!Uz(c7J zM&#}YJOhaN!n{VZHd^gC{KC{vqj~R+O7*%5TlM-4w8;@0X7}J@3rm;B)+P01T_2h5 z6qwNHn_L_AMum2UY2-Erm`y*2PE|`E!b?BT
    VwJZ@yXTwcAF)bX!w9F~9Xmw}z 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 gJkPj{V=?}RXpH3+rtPT|CGi7 4 * self.read_timeout: + break + timeout_count += 1 + + data = b'' + while len(data) < size and len(self.fifo) > 0: + data += bytes([self.fifo.popleft()]) + return data + + def write(self, data): + self.tn.write(data) + return len(data) + + def inWaiting(self): + n_waiting = len(self.fifo) + if not n_waiting: + data = self.tn.read_eager() + self.fifo.extend(data) + return len(data) + else: + return n_waiting + +class Pyboard: + def __init__(self, device, baudrate=115200, user='micro', password='python', wait=0, rawdelay=0): + global _rawdelay + _rawdelay = rawdelay + if device and device[0].isdigit() and device[-1].isdigit() and device.count('.') == 3: + # device looks like an IP address + self.serial = TelnetToSerial(device, user, password, read_timeout=10) + else: + import serial + delayed = False + for attempt in range(wait + 1): + try: + self.serial = serial.Serial(device, baudrate=baudrate, interCharTimeout=1) + break + except (OSError, IOError): # Py2 and Py3 have different errors + if wait == 0: + continue + if attempt == 0: + sys.stdout.write('Waiting {} seconds for pyboard '.format(wait)) + delayed = True + time.sleep(1) + sys.stdout.write('.') + sys.stdout.flush() + else: + if delayed: + print('') + raise PyboardError('failed to access ' + device) + if delayed: + print('') + + def close(self): + self.serial.close() + + def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): + data = self.serial.read(min_num_bytes) + if data_consumer: + data_consumer(data) + timeout_count = 0 + while True: + if data.endswith(ending): + break + elif self.serial.inWaiting() > 0: + new_data = self.serial.read(1) + data = data + new_data + if data_consumer: + data_consumer(new_data) + timeout_count = 0 + else: + timeout_count += 1 + if timeout is not None and timeout_count >= 100 * timeout: + break + time.sleep(0.01) + return data + + def enter_raw_repl(self): + # Brief delay before sending RAW MODE char if requests + if _rawdelay > 0: + time.sleep(_rawdelay) + + self.serial.write(b'\r\x03\x03') # ctrl-C twice: interrupt any running program + + # flush input (without relying on serial.flushInput()) + n = self.serial.inWaiting() + while n > 0: + self.serial.read(n) + n = self.serial.inWaiting() + + self.serial.write(b'\r\x01') # ctrl-A: enter raw REPL + data = self.read_until(1, b'raw REPL; CTRL-B to exit\r\n>') + if not data.endswith(b'raw REPL; CTRL-B to exit\r\n>'): + print(data) + raise PyboardError('could not enter raw repl') + + self.serial.write(b'\x04') # ctrl-D: soft reset + data = self.read_until(1, b'soft reboot\r\n') + if not data.endswith(b'soft reboot\r\n'): + print(data) + raise PyboardError('could not enter raw repl') + # By splitting this into 2 reads, it allows boot.py to print stuff, + # which will show up after the soft reboot and before the raw REPL. + # Modification from original pyboard.py below: + # Add a small delay and send Ctrl-C twice after soft reboot to ensure + # any main program loop in main.py is interrupted. + time.sleep(0.5) + self.serial.write(b'\x03') + time.sleep(0.1) # (slight delay before second interrupt + self.serial.write(b'\x03') + # End modification above. + data = self.read_until(1, b'raw REPL; CTRL-B to exit\r\n') + if not data.endswith(b'raw REPL; CTRL-B to exit\r\n'): + print(data) + raise PyboardError('could not enter raw repl') + + def exit_raw_repl(self): + self.serial.write(b'\r\x02') # ctrl-B: enter friendly REPL + + def follow(self, timeout, data_consumer=None): + # wait for normal output + data = self.read_until(1, b'\x04', timeout=timeout, data_consumer=data_consumer) + if not data.endswith(b'\x04'): + raise PyboardError('timeout waiting for first EOF reception') + data = data[:-1] + + # wait for error output + data_err = self.read_until(1, b'\x04', timeout=timeout) + if not data_err.endswith(b'\x04'): + raise PyboardError('timeout waiting for second EOF reception') + data_err = data_err[:-1] + + # return normal and error output + return data, data_err + + def exec_raw_no_follow(self, command): + if isinstance(command, bytes): + command_bytes = command + else: + command_bytes = bytes(command, encoding='utf8') + + # check we have a prompt + data = self.read_until(1, b'>') + if not data.endswith(b'>'): + raise PyboardError('could not enter raw repl') + + # write command + for i in range(0, len(command_bytes), 256): + self.serial.write(command_bytes[i:min(i + 256, len(command_bytes))]) + time.sleep(0.01) + self.serial.write(b'\x04') + + # check if we could exec command + data = self.serial.read(2) + if data != b'OK': + raise PyboardError('could not exec command') + + def exec_raw(self, command, timeout=10, data_consumer=None): + self.exec_raw_no_follow(command); + return self.follow(timeout, data_consumer) + + def eval(self, expression): + ret = self.exec_('print({})'.format(expression)) + ret = ret.strip() + return ret + + def exec_(self, command): + ret, ret_err = self.exec_raw(command) + if ret_err: + raise PyboardError('exception', ret, ret_err) + return ret + + def execfile(self, filename): + with open(filename, 'rb') as f: + pyfile = f.read() + return self.exec_(pyfile) + + def get_time(self): + t = str(self.eval('pyb.RTC().datetime()'), encoding='utf8')[1:-1].split(', ') + return int(t[4]) * 3600 + int(t[5]) * 60 + int(t[6]) + +# in Python2 exec is a keyword so one must use "exec_" +# but for Python3 we want to provide the nicer version "exec" +setattr(Pyboard, "exec", Pyboard.exec_) + +def execfile(filename, device='/dev/ttyACM0', baudrate=115200, user='micro', password='python'): + pyb = Pyboard(device, baudrate, user, password) + pyb.enter_raw_repl() + output = pyb.execfile(filename) + stdout_write_bytes(output) + pyb.exit_raw_repl() + pyb.close() + +def main(): + import argparse + cmd_parser = argparse.ArgumentParser(description='Run scripts on the pyboard.') + cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device or the IP address of the pyboard') + cmd_parser.add_argument('-b', '--baudrate', default=115200, help='the baud rate of the serial device') + cmd_parser.add_argument('-u', '--user', default='micro', help='the telnet login username') + cmd_parser.add_argument('-p', '--password', default='python', help='the telnet login password') + cmd_parser.add_argument('-c', '--command', help='program passed in as string') + cmd_parser.add_argument('-w', '--wait', default=0, type=int, help='seconds to wait for USB connected board to become available') + cmd_parser.add_argument('--follow', action='store_true', help='follow the output after running the scripts [default if no scripts given]') + cmd_parser.add_argument('files', nargs='*', help='input files') + args = cmd_parser.parse_args() + + def execbuffer(buf): + try: + pyb = Pyboard(args.device, args.baudrate, args.user, args.password, args.wait) + pyb.enter_raw_repl() + ret, ret_err = pyb.exec_raw(buf, timeout=None, data_consumer=stdout_write_bytes) + pyb.exit_raw_repl() + pyb.close() + except PyboardError as er: + print(er) + sys.exit(1) + except KeyboardInterrupt: + sys.exit(1) + if ret_err: + stdout_write_bytes(ret_err) + sys.exit(1) + + if args.command is not None: + execbuffer(args.command.encode('utf-8')) + + for filename in args.files: + with open(filename, 'rb') as f: + pyfile = f.read() + execbuffer(pyfile) + + if args.follow or (args.command is None and len(args.files) == 0): + try: + pyb = Pyboard(args.device, args.baudrate, args.user, args.password, args.wait) + ret, ret_err = pyb.follow(timeout=None, data_consumer=stdout_write_bytes) + pyb.close() + except PyboardError as er: + print(er) + sys.exit(1) + except KeyboardInterrupt: + sys.exit(1) + if ret_err: + stdout_write_bytes(ret_err) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/venv/Lib/site-packages/click-8.1.2.dist-info/INSTALLER b/venv/Lib/site-packages/click-8.1.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/click-8.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/click-8.1.2.dist-info/LICENSE.rst b/venv/Lib/site-packages/click-8.1.2.dist-info/LICENSE.rst new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/venv/Lib/site-packages/click-8.1.2.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2014 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/venv/Lib/site-packages/click-8.1.2.dist-info/METADATA b/venv/Lib/site-packages/click-8.1.2.dist-info/METADATA new file mode 100644 index 0000000..a950e8c --- /dev/null +++ b/venv/Lib/site-packages/click-8.1.2.dist-info/METADATA @@ -0,0 +1,111 @@ +Metadata-Version: 2.1 +Name: click +Version: 8.1.2 +Summary: Composable command line interface toolkit +Home-page: https://palletsprojects.com/p/click/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Changes, https://click.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/click/ +Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: colorama ; platform_system == "Windows" +Requires-Dist: importlib-metadata ; python_version < "3.8" + +\$ click\_ +========== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U click + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + import click + + @click.command() + @click.option("--count", default=1, help="Number of greetings.") + @click.option("--name", prompt="Your name", help="The person to greet.") + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + + if __name__ == '__main__': + hello() + +.. code-block:: text + + $ python hello.py --count=3 + Your name: Click + Hello, Click! + Hello, Click! + Hello, Click! + + +Donate +------ + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://click.palletsprojects.com/ +- Changes: https://click.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/click/ +- Source Code: https://github.com/pallets/click +- Issue Tracker: https://github.com/pallets/click/issues +- Website: https://palletsprojects.com/p/click +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/venv/Lib/site-packages/click-8.1.2.dist-info/RECORD b/venv/Lib/site-packages/click-8.1.2.dist-info/RECORD new file mode 100644 index 0000000..3cf9051 --- /dev/null +++ b/venv/Lib/site-packages/click-8.1.2.dist-info/RECORD @@ -0,0 +1,39 @@ +click-8.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.1.2.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click-8.1.2.dist-info/METADATA,sha256=WCMSDiatET4ro1yNcO10GKotBhRv7YtuyclUnLA3Q-4,3247 +click-8.1.2.dist-info/RECORD,, +click-8.1.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +click-8.1.2.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 +click/__init__.py,sha256=qnC2TpowVpRBswtOTrLryHx5cwND4bIFA-Bq3jO6iB4,3138 +click/__pycache__/__init__.cpython-39.pyc,, +click/__pycache__/_compat.cpython-39.pyc,, +click/__pycache__/_termui_impl.cpython-39.pyc,, +click/__pycache__/_textwrap.cpython-39.pyc,, +click/__pycache__/_winconsole.cpython-39.pyc,, +click/__pycache__/core.cpython-39.pyc,, +click/__pycache__/decorators.cpython-39.pyc,, +click/__pycache__/exceptions.cpython-39.pyc,, +click/__pycache__/formatting.cpython-39.pyc,, +click/__pycache__/globals.cpython-39.pyc,, +click/__pycache__/parser.cpython-39.pyc,, +click/__pycache__/shell_completion.cpython-39.pyc,, +click/__pycache__/termui.cpython-39.pyc,, +click/__pycache__/testing.cpython-39.pyc,, +click/__pycache__/types.cpython-39.pyc,, +click/__pycache__/utils.cpython-39.pyc,, +click/_compat.py,sha256=JIHLYs7Jzz4KT9t-ds4o4jBzLjnwCiJQKqur-5iwCKI,18810 +click/_termui_impl.py,sha256=qK6Cfy4mRFxvxE8dya8RBhLpSC8HjF-lvBc6aNrPdwg,23451 +click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353 +click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860 +click/core.py,sha256=k1SieY7U6WvGvNY8zIN3Ypko1FwpPPImlrcgRaOaoqs,112646 +click/decorators.py,sha256=9QdOGevZlXJt-BysBDEvkwaCQf1wu19D2m7tVp4Plqo,16302 +click/exceptions.py,sha256=7gDaLGuFZBeCNwY9ERMsF2-Z3R9Fvq09Zc6IZSKjseo,9167 +click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706 +click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961 +click/parser.py,sha256=cAEt1uQR8gq3-S9ysqbVU-fdAZNvilxw4ReJ_T1OQMk,19044 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=qOp_BeC9esEOSZKyu5G7RIxEUaLsXUX-mTb7hB1r4QY,18018 +click/termui.py,sha256=ACBQVOvFCTSqtD5VREeCAdRtlHd-Imla-Lte4wSfMjA,28355 +click/testing.py,sha256=ptpMYgRY7dVfE3UDgkgwayu9ePw98sQI3D7zZXiCpj4,16063 +click/types.py,sha256=rEb1aZSQKq3ciCMmjpG2Uva9vk498XRL7ThrcK2GRss,35805 +click/utils.py,sha256=33D6E7poH_nrKB-xr-UyDEXnxOcCiQqxuRLtrqeVv6o,18682 diff --git a/venv/Lib/site-packages/click-8.1.2.dist-info/WHEEL b/venv/Lib/site-packages/click-8.1.2.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/venv/Lib/site-packages/click-8.1.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/Lib/site-packages/click-8.1.2.dist-info/top_level.txt b/venv/Lib/site-packages/click-8.1.2.dist-info/top_level.txt new file mode 100644 index 0000000..dca9a90 --- /dev/null +++ b/venv/Lib/site-packages/click-8.1.2.dist-info/top_level.txt @@ -0,0 +1 @@ +click diff --git a/venv/Lib/site-packages/click/__init__.py b/venv/Lib/site-packages/click/__init__.py new file mode 100644 index 0000000..da1c3d3 --- /dev/null +++ b/venv/Lib/site-packages/click/__init__.py @@ -0,0 +1,73 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" +from .core import Argument as Argument +from .core import BaseCommand as BaseCommand +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import MultiCommand as MultiCommand +from .core import Option as Option +from .core import Parameter as Parameter +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .parser import OptionParser as OptionParser +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + +__version__ = "8.1.2" diff --git a/venv/Lib/site-packages/click/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a426f31dce0ac31d6d80354f2571a3ea1ea79b2 GIT binary patch literal 2617 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

    2DT$BiLEp*1cJ1zVy01l1gxJ~U z=~Oh_3l4pZr8K=0{JoDTD4HT?h&Rdk@RGdj|8qQ=5?YYC7SdEwBLy`Jrb@5fxRr*t zj%up|^D_!`A<~X^N!%A=p4UvWP-Bn>R|9jF+B|CEw?Iz+5Tiq!S=GhIQI}|pB}A<3 zB>+HC6AUX)|2Xd=^OqCa4NRDrR$RiE)5*ZZS4u>ubnuH6`;qB9GWWs}|4%VcO?uf?SG#?O~J2K%0Ux1s;ktxqv#&=vLs$H`sxokz(pigd^dgg z8Z7k5nw5mXWLegXK7x{=S+~re)0GvuQqOA{yd}pkwnYC3DJV>?cZkBrDf%B9z?NOp zxd5^8HM5b#6Tb+7f1Q?rrA#<2kHCaPo|od_)B#flTc9W1f`R_XH2yco`n?`q3CsTN zDt_s}-=M8j7vVCF0C{z6ESH1d-{8kM+sbQp<1MrmzDR+qbz$gOxUFHOZQl3SxCpa( z_xLI#3fVn+M0jU=iJ_4Ffz1o)oG5;P_ShDl*`Iza-)jhL35GeHWzD3M)V z*?;I`33#TU&o069x(<&NdPAbndkVDTHL-7CpdAysj1SnhJuZ?Dyf^1)|Ar_Dcs@OPs@IxEjN=ZcY zffp4(uX51QU&`}4>K>IWGgY4zRLY3sv%h}#%28|=Q* zkg+RTsQPq}!co9d1cWD!Mr=|>4abNa`Qx_X3WBA-&po0=2+nBkg~G{zEF-Gc<;%qv z!c@7enpFl>`BPK)a3LXei!%sgm-)%h!X$pkLG@A8N{ngff6d~Q{Fn*1m+u8UV11XAa#pg0t7)3AO_kLfeuaE6oi@u#ofr1sHMD1 z`47~|?o%WMH6ZFGcDOS;!|w zN?g*o1NJ~gU}>w)m58_Mdba%5FHpp5o6P6h4*G$=WA%x)PAi!nEzH#UC|y{YXGZ&0 zz0$@9r zKXKiv!YWXtUPl#Mv8r`VDXAj#eM-zu#avw5>55+y+*1Sq<%1gvS#CbU=nKzh$(g@9 zvO1Z{yf9;VT3F?Lh{rR^^La7vx4m#We`~Q!i?v(C+O|OWj+-Z`mbrdgdXXotjCr~! zJ-6EZP3&jj^5NCMUVPE$ z*}<*IKJ#H_6qIBm2u~S)WHa#0wGrO#XAsa3vSiqnC9j}!6H?K~gb@ilpwW#+FCHlJ z8=>SislXd{!CO+(Yii%FTS5m=K`%2|nI*YgSK{xv#mMR71yr>$^@Y6y1=t55;+-N_ zU~h)R_tADMtZu?u>_VjP0*W@-d~+Wx4$TB03u%w`7^4xw_c5dwdrjDyVNkEYZrsKT znSm6k>HHHc#()-3ny%HYqi7FwaW7an3#j2fK%28(qbu$V@+61NS*vm_6}4 zQ)j?d2&lpQKSEpU7RbWn=U+jL>wzFtuw5Fm2<9%kc-Vxd;otB2NJ^8;w3NOh<*ZN( zV5#(7DUTOPzS*I0TV7@kE{ZEH)C1JJ4>>GS&qpsxJXm2Zw#R;n0&h}7V9P8Afq2)f zk9Y)}XZU>R?l8L7t@i<`2s?%M#)swkJTqg9hw`1x|1{4=b=um!k)qC6GbVbvKE?Nl LzQc@|s>}Zc9W{KB literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f915a2c85b6a71d179ea275a082feaf97015c5ec GIT binary patch literal 7784 zcmcIpO>i8?b)KG?ot^!~5(FvmeR3(R8b4XRHa>!*WRXI2pU2;jBgJO&MUe7MD z_+c)p%+|bl-TnIiy{})ttrrSe4bKmKvHVx3HSJfF7<_CbuHy~=L)WxLjcc5Fnv;B) z!&F*#boDhH6JOm+wJpbDBx8V2J88w6;BCiNd`q#SH0q3E^oP;#y=*!N_;uQ}f}8^7iNpA2x~yyPqbU7`Dt#5Dd6Dnx z{H5(0-xR0#C~AHS-(&c`hVOBF&vMPF@RIX7zHjgo&YS$Ca}NA@>eFjueP{fM7`tn;_{g-51yeOKPw{37zkcI92-?;vknTo+?voPYbV;Y^_X zhNyy0lH>{l{ivxQ)94j9`MXy%$`QBtd+6tV{sHPwsT_0lWAPox41OVbOyidyoBTt5 z1(MU8&1;owUyvo#DlASdZ%WYwV>R5yy}ATvC!D)R#SORRM_@7yHw;@_!lgvI;jVju zzmf=V1TC(F)3Y;EwT0>&iLY(C!SbqTsJio!TyI3{QlKPtV`*mY1{mwsSnbAi6uaqBvUfdsq^{!)=Rkx;C;|jw!f5JT z7bd77QlO@yDPe)P6itgXcw5+@8AY>-=BVyU&dKmR_N@?SW;fmC^=1=GVBi}YSmoI8 zgdf|jNVI8-Vq1zj4}5PkHhtb|$5u0t?Rpfa!h7{K5vRjQwzvr6Y`DH0G@GG_LdV87 z3+dOrS|uCXuIty^!gb@U>$U^F?h&7N-5;&jy`)6a3Q0m(X%R^gF+p}TWex2#Tf*a^ z`%IH>k^qq}7bh;?T?~Z`?>?xn_~P!3pt0T-eiYtasmuC`xDf=>X{{^^ad$)b8+WH$ z%XdRe?%Z0vaUbR3-GoOGAwrWVG=r>k*3NzYH+o<(YS zc5_BqkPB5FS6iN}Hw|7wH8pt#B+fTw(01#bOA&_h6lImrkgDL$2~X5RQAtT0fm&=o zkgce`?1@A!)%5Bsp_5A#%8fQ*S=;+0&ryx+iimm&_XDKEVGs>Vn`a}s&ay18=fBc* z`7UzzSp$ucp72$W9c$qFBh4{B*P4tQ+ADTC9)_-)jE&Y@UIl664auG~Eb`Ww7mg`uuwu#AAS%xisSvqLgc^zqy)GKU^ZMPW zP5y{PREO_@YvENL+RQd2Jeb6PX7W-1l{Q}*t;H< zXaF4`^-)chBWP!rea0dn0mrO<3LK!aY)|c zT`kJo*X6w^`y|J$r@(@a6?URLPgB`1v|j>8K0`guj%iT=l0Tuox!y^w7Ds>*6}u*B zZR9bKt!s_C?*~ztW>gM1FZ=85Wg*K!v%I_+iLgu%P+nY^Joomy=ccP=0bJyQmwV{A zP+{^Nw6pS;Kd0w!KiV4V8Ch9K8OY1!%20wO+3(f8by1~EWzv$CE6&fM-<~}#$pQ^a zSrh3)oCi8h*TYCvieIYE&CSfYGqcsXv4yFbnmaZ=Gq+H^5t~t~Eno(o5NmOIWA?P={dr}1TExJQavhdnbd;4~B4#ZvBw97UI5AP3 zpYJPKZDzr()@BxO-g0kOZ_mu#*(&Z*>vBbxhh?QhS0lpb`Qq%X1Txei_ykIvwgIQm_i)`=%NEnbj}p6m2KbPH$6<9Wt`oixBHcF$XFA%GT$Jx>Pl0AY77Ht9)D(OuAgL6Tg)6I$ zjdshA58WR7zB_(rp*pWTkcDb3vG#(R+6&BGPSb*ZOk|PBBuLM4&?dFsetbMq9wu=gC~}Ab&u6cI^1ODEM={A^F5~e8MqGOUm76%XG|L#~c13 zNZ*!)v1;N?amMw>hO%eVu|!(fJoT7y*z+S3_ML%DR^+&iYhQ+Eahb~_r$D*5;xV2> zex4VQUlcZShjd*O8s>-j2$141nO)^bTOcBQRi(!BwxA>fvT%Z@z2 z-H@N4%HQEluuk8?B~sltl7%`?_2AsGPm`W$3HlH5)S*JUaWG{05$o#mD8`^aF*@ui z#<7)$-J0YqB7QM!YEf$Qog{jJ$c27^#%uLulQE~`Yzx<6sHfi$v4Jg;E}EC5oiQBx z_3&PtsealJYf&rklQq_pHLjD~Zc9?t{vP?tbtPXjkJB)Z9~S!X?%67PAz#+Rg*x0n zWzquDZ#8AQgmu^r?FU7;R@xJ|RpbQy2w}AT;xpBEfB&11k4R0vo4-o3< zfz7r?4;Y>j1MYtU$^9dwO`%b$5$3^QQ=krjVjMYr9ZlmrfRmmqE1LR%n)-yu?xl-! zE}=h(y6!)syni4b+dZxUseQUh15$%gcNpj}st{s(y7Vo4A&U#$5f9UF)rLY%6~g znFkG%f(cTrVU7NIV66LWI1#Cg>;dDLMX{AceqwvP<=5rr)QrM}*j|$X?o`pHY@lJa zL$NjHZ^AZX1O8ZI5%v^(9(Cir2dUVAfTY`AY%T|Zmn?3Z>hEl>;uQA$4hsGnZ%CMC zv!kZYO2%+z)EqX7hNM!ML!ozjPQY?M2GJgG-#7>dBltN_aOy_@^{0$LpCJFI=0n66 zo>)<86?X&z(60UzYWXZx`!cg0HOaO2L~Nm(RwIH-9~Vj4dv|jzekCIhq4ijA1#vOC z{tG?_rx(5r0Z^QuU$`+y}4M+N4 zAlL&90t~p&O#h8#$=^r*E~WdL^Ov8Z!QR|t2mH?DmXt%Q4%)bY$;~V-49-p>T&L|! zPX3sZduCPIHLGAf`Zow2G_6O-|9_p9{EUY8T@W=Fc)VY4F4%Pn@*Xr7@;!jNbTCTN zY~>}!hk7Tx+pIUDK(<6kM_Y5hLK~>E9WYx!f>4wLL&GAu?&?Sbj6Fm!%s-AJ4XVD1 z(C5yeGoR2P>|d}-MDy@0>kPQzm3bJB=wYe z5Uj`_5wChUC|Z<>w%=frU#JqJ<>xwQ6N(I`U^!ZZ_Bb zhI07#8q^78P>9S@46nahJjVIdBLTsG8?7 z#-s__{3NPTByI>KPGjipwR)65s=embBMPF+pOD01A|pgzCt3SG0_VcJbWz1Z<|BSf zMIL39B)(7NPl!wsIRb(J{5{0tE?ty#1k0C*{4S9$kv|}Efyi4#wlA>qCX!AM!zZVv zar1Thpf`{t+(?p=g5h$B$O(`*H`OLPsqO@KPEy(1L@tp)W;@oZ!hcD4f&fXK4RVdR zfCxdnBtVpeB#FkPZ{vR%+MN(tu{}}uJUVsbA=g#0D`l7z<|HFjMiSdI%0;bv$pKFL zKA=r+Qc|5f_Lbx>h-Lcg}m6!GM(G_Q@9HoO$Pc&wH-F^ShtlscqXfl;Q73oAryOU&?0wg?^;} z2KezLpVp`HnM{xgvP+p+_nn>1+V9+K&c5@rdHXKR7VNt?TjD#nG_YKrEob#kera%d zXm-f13tSJ+4%>BcX=HiZ>^8eDaXmUaYS#lx+n2Y`j@fmY>m9Q@?0Rr%eEE*qJGdSS zhLyf2h%XiM+Y1iAh-aWh9u1C4vGrPyGw{yLBcCTHJaede9U3R^L>wUBP z?0S6Z?&bZn`|bJ;uJ4(>$F3)qD$DoI-fP!8xjry^z^->KeQ5c<+57DJPOd*Z`(eA@ z&Go_AgLb`V>Cp1z?4(`q<$7v%%C7I?`ta;wyWY2S|MHR9BX)f^*GFfM+Vy^}r)Q__ z`W~(yn0>&mD_kF&J!aSUa{b`!gV{{IwD{0l+3=e-2M^Ca!WbP``pEL5vya-dj|Lwi z$78{Lq#s+z?a2fmels_Fyk5Hb(cs`a+1ZZ=hw6{>>=S&Rs6QS|Di67)-p&VygZtko z%ziSM36APX{mCF3Outy{*YmTV;m#+5L)sfw18BFqPvi>Z2XZU?0ILhy% z^%;IY$M2_tll(qee~#bh_@y%N?|S1sqjrFvWM^03@Yu1jHk zwO(s~CreE{Dz>k#)?4p#apK}isLUfz)q-=iu(n)pTREdAmm2dgpMGt=zS?fAG|5^} z8^}5KY@^j`G%v-uc{cPyiZ`S-wYoArXRlLh%MD3ub*a%-p*$R*u!Tmr z%w=gcTv@i8#a8?3l3tyt2J~vaUFFdwx>#+}O%++KzqVRy2Gv@4NzE$M=P$4DedluR z^4Bmt^fUJ8m)GvwzO1fU#?eLYZqye+SsbBEL3Wh zOX14eDvxUIN~;YJR<1PKmz8y>!e7$OV2V7l^b?J74bbwJ>% zdv|84+{v~(h51^m-5Gh7DQ)Zev#!r8{9i}m`8uhyHdzW7Yz;)|_DyMA<)b zhxJR0Ks^jq(tLp=`V+`)=W~)z>oN%dmkGz(*^OK~cRkmd2r}mZuW)~my;f)z0>G>d zVEr5vo}7iB%LRpvEGghfT51s;>pg#$^U#_+q>vadSX;i6e z!L8M^i;H%q4XOk#fwzt9_1sVKeLnMUc4n&3DQN9?^6QP&aFDE>g6f#cg`eU_cvgv4 zbE-#0tNM%FY+0q*aE9y7U`!CF@`fHdMZjfHn z_4C=xwOlr{{^&+-F$>PlgonZ5xhjn}4E_}Nb9Vo!4HjZG_d4%>0fziq?#FV?C*1e3 z3}xl7{-iH!e_Yn9uB@(iXo226mN`Z#EWX_O6P{32SYTp9-0Fd(*O<^&1s)@;0ghm6 z=)CYZuw0M$h1N7f8;;U9hbcv`!U+;THlc2Gvi07vov5u{5{|5{iJ_>1kXFK0mAtLp zB$;9k+$Q{1%8v8d<*&o%c?NilHDYQ~T?vDF*x3=(=a;~?YM{H-e>#NmG%c;eBtf>F zSu}Vto;de*?kkyvY>*EMZ;V{awZUf_;IlV#3{z?S?>^lCdsk{K=U3M1EFzIgn|N_j zhHFGitvQBLF0ZUD1)@>VyH^L68{9P!j*cL3j0s-d3xYe4Q1<^@#X z7>1UWRI3Q2g(_4t7A_Exnn&|@{eKvldEy!LK3JkND%J(gQy&|a=z{gT>b5^Pq8 zWF$M9TR)-!tlE6EstmDsKtIIOk1xS`EVZ7PjtYQ%%To^M?Lk#nys!qj?G!GqtSp6J z(4CTU)&qdp+HgS)c}a<(h!=HbNN?}fwT5JB8G#+#Uk-%=^y&K37(dCU^%zM#BhebM zIy;-M7j72c$;_7g_kjN{`|m;jJrrn!^PTN6MmoQ;7S7jeFEW!6b*eQh$5|S!*73O? zt2ft{r%#j2c@o(S8Zc$7asg1wUN#QUAS4FvLoLVXdA1H-r*a(m;qvi0V_c>^0+|*E z6~9z<#Zl8rg$Js^O7*p971WaTQ@ME6h?cKndb;u|i;Bez?Lxc>jvuc)Hhs(r5DRKe z=+i4v*C1RG?^>_S&B2~FBsa~?Rn*=UK;MeEUGXQ3GP^R)J^$RZ=Ppo!@wySKIUJs5 z&N@TYDy+pC99k7Aqx#BPZAqk;Ujx-@urkj@L4)2Y)YjT7o#B(uJ^Sp5nN!c4ojKhZ zK0Wit;h77aVSiD5_QbhP$yJ_0o(6vcy)Yp8{d`(NzU6FT zTRG?dW$oD}Xwyo5QZF3HC(!l+NKo)LTSUYi2oU@02Gj|V7;__Zw<<<}j-_F(Ld%D1vrD}S#u4W42qsMR zaA$pYpVD>)yWS|EnAlS!U+`g`-%B|IR+r|0I)j6hbO@D3VfH}$ zY@gko3J!Buik|y|AEDIyxfM4XSLM+4eREDkyq2z zIJUdBTAxQV1L=k8G`U#0xG36d@eRdTQbgT3WhFWnLV#!X!~dLeW}t32s7VU#*}h zIVz68ux?vYb8TLPP+ zBqf)%2~LW3n!ps@jyP8Od*yP2YLNZw>cf==stsuVlGPCttCd!Lt|AV$UJHYjE4_LO zH=M_9j{MkGjL3LZ1?-ov*^vRS-+<)NdiR>U?wd_4w8>R2r+ymAOA1f8TIT-gdJ~RN z^De&6`XS2K{YQ%gP|GW@fkEBD6>Ms{+!)+e!YvB%U8T-59zfeG@I!!L1gVloTZ}&B zZRzS3rKlaEec~TKbuY+i}1UfV|j1I$`1X^+(;n zNN6zD^@hlA6<7nr;0)^v4OCKUrMuSJ;5}=ra!SyducSb8i>Ahtkw7ksR{dwV_&+zZ z(gfnDs2PF(?Ml+ZNY|V!3KbYMT3R6JzfAjZyTXDM)}NFrwS@(tC)SHip{XQ(H{J8g z41h;@c%^pLt?I6kj?}5kwN>Vb7S9XCX^tkB8ZQGLhAF3cGQ9I~34RtUfSiAN6QfEu%HiQNSME$?ih(tI)1k1J8P!Q4>#Oc&1aJ#X* zw(RQ@?eUoaMIzE$||y5Ys$lH zGy_JE9y=zjRal#6bXYM9w<$=VyaJ&NotBxIXjYv5?+S8v>$s>WqO>8P+Y}W~2dfIj zf!BLTlCB|YyMEiCQ|E1pVrW4aya{z{X}FyZrCI6j-E9ZkgC^rdw@?l?Z012tu1&5Q z8>r>RrOUT#m2{`A74UJZwoq?hW$mx7iu{WFf%rld(Y2DG)eT?^AOhgUh#Yq#$pFbr zBlJ^m8un2E&D0p(W_(m!opC_`L$_ILij$!mOnT$h1}zh0LRc=M4ZlrET?F!!U&$ty zY1|?CNck(MTy9s)C3vbBiK=6s0n#FANcApugb}z+Iex|?1_vr9t-Y43R|j{EFI`-@ zf+Y%qWrnC^cxiyw5zk!+*CK-96mNj0GbY@+Ee3y>LtxD!sI7#x2ICHyH0>G}{@Lxu z=VeTR5tu~$j_6Gqc^WtAkmRi!W*Afe7jYSu-D55{BnAyIkU)tu(e)jfGt6x?G~U8^ z>zX}rR=49p6eB4l0w!HeLBh94`>vWj^Je2fhj?fxJgRCiRq&F{_^>tuzq7j@-VGdV794CX$AUytPI^D~L7e=YupSMq#`ud8pZiuPm*E$BjKemJ=N|5)qR~kIh<@6Eo+}l8xLLibHXhTTW1FQpDvr z!#uDZaRCgku@N%>1A`9+IFR6_|&+kWkw-WSvZr{_%KC>a2|GEkW+ej2%-HtjzAE}wwT;=xs z$XEBx_!w$zwgyU}l4dJNi`jl_Gls zd;uLs_yjI*8*K{Bx>LGkMn+`rSy66e-m`X%+ZG0y8@~A+S6vSs*J-)k?z-E$?zo|I z)9pu{{)Q6;j`CZ6=Z5b@o4%9u&q?}McJK+-xBOPbLhX(Ukw_#_m$xl+A^Wg%Hoi~d z8=k~v*NBYU#)dgE{@Q%q=olmOZ_Rfh`}^jqA`D`jHtGxG!u6Z{y>F_cd~E#Y>dNx9 z?}ok#uibPv`u??-gOULV!KjZnWK)F$%!us}3T6FwjcLNA2{AxYTdf!&e z7@JXSx7;vNRovaTq@(H#(u$QS4p*JVC9X`?w}2-hWn$zjIpyH@51h__dZW|pbX~=% zTjo%f*CyLrTXNP1krVX0I}V@Av?fy+^m(VJO)DVsLGE7YEH1lIq?XrK^z%AOtkvp{ zb9J*5I`}^b{q~^iblRxFMfIW+Qj_wxl^;T>byH!4h2#Wn=v&_pBj0nv9sKk@N?Ry7 ztu0&TL48paW6e?({KPr5t{z27%Uq<-;xZZMkw_=@vd4^FCjj0LEt`)99|77`_v znJZxC&LZoT;nAqRf>q?Mcl{s!@XDJ@&XqT7xp-EKqZ#dN`*FeVhXduSV<^xP4MTz- zu2@#b-;n*qYK8H?YrSo}ZMH4X@^W`7x2zFYt?ky4?dw>UeaG#mOOW+L{+N!I@*!p} z>P)}&#N6z3yUxu(U6;8D4_6g*cTKdU)5p}f9;b_=7C-YfNHCt=Wr!NLC}S^_O=5RIn$6n&h7c_vCtOVwPooB>*41`nf*!l)6X50X+xtm zdMvi0x0ak|h*iEdo zGk2|f=B-@6jI^cGBQW=Yp_U^wvTJSGzrezNFL&GgsX4N?^1J3Jw_)wKhPB-4@bY)u zKFk&tQ-HO^i67hRo1JJg0I0)+r?9PUGUtVuFf2R%Tb+;=ymn4QqT zxbpe{=D+8^qEw*bQnT3yO=&jcf^HcW?>L64PT&&fpuXxUq-r^Jj!$Wz)u(uwM&4KO zC}cUqELgT#vC3A-wD8aVu<)T>7(Ul?ulr3|UzzTWf%%X}srHVs8dg`jAgzxL?o3wr zW%hydWtj7i44?jZE!Ipi=@_5XD=mB)Rp{|O!<5+dV~147e7eC?#~0KR?!e=YctNY_ z`C&_SG!@Lnx$FLpy2z@;E~Jc(zd3x_W&VcCxQy)rm|bcWOwWc|EqeJ~11eQ`FL%rI ziX-zEhF5woH?o>gFW8M4_a~@wMOK${z&KF$Vl>bhK}}hEiRBl%R8Q?cSgX56&PQyY z=6%#qK23z~>lG~c3)-8&dIAe*XxaG|TI=+kwTxUy9X?q{F?8>k)xj>`lUx7|UPtR-;Hay%E%*$Y`csfe6OiJVPF zSFbxaKus}R*YkXDdD(fceo2ac1PD2p-o)HLgt9*bf~buef&eGxu6fG1h1uT%ARPuQ znl|M$ZL%qLbpaKqMO?t9WF^=6V0tBA$D^=-3s$mOHiw^;Ma$w%h~;-(v!;?JvgN9{ zX13UbUZ~alP-e0DkOo$VHm7KK&dKKG!D>4^{&GtCp{QW1iQW&N{g?`^$pn=dLL%Fb zi_IoAvk8ol!N^J;W5gxo(HJo|C8{5Cl;o_&k%dyptmMic+P3;E?%_oCysNti{3<)24sS{?GD#go_6w9T~gUZBt#^wQj*QzhmvfYaSTkxo;xSM)M4lehFzY z)%u;f9GUN!k+qfcY+P*yL?YA6YsM}-qTm(5*=-nob6~3N5mL9|-i~ZwamoFAbV6e> znL26Ea;pmrrkf=Z1RyNvGkhzJvl*gh3J8T2SV>*?)FTrf1ur#l+(8ud+^EAGI}T>n zZEZS8?}O)*fQvm^yN3Rt;pAcJ(97cxt0f6q2sCopB?^@>cHf~MrEr|QB|**rm8B;0 zq}BmWL$oO)LZAZ(3f~-KhYQ(tTfpq~9mjLSO+UmqztMJff`J4o2+^%gx4%K|??y}V zqUH9bA}%xD)RJaSby(tEsT-Hrd(OG`8zyHzDVZwoenm-#B&}!%Ah}8jC4hbyA!EXS3qs(IyiQStfGFq<1?IXUgTF z4~UsS)uHU#0~?cy2i1G1qNNQ0MLe<^33p8?T-LJ*#S>jRpmY@b>hSZ3yEzvU=`BK> z&*NKLB*HN8QM*7{hTDFt(+1)=dOZxkG*v2diuV=UR>6&q2lYyqUA=ygG7-N^_=?~5 zDFqY?=UlpBb5bYXi>Nuskxh>|43-8~vlU5#@Kj zX~M3V#t)4C!X?zSX2V@8L`7otJ<~+kX|Lqv?y(j_-9XuyUD$!k#;)0h&DeeyPs)3< z_l&&?H(&0-eH(#+-_7kB3}yoTQDS5_@dfG^SBGDC38CZOHc2RfE9iqOit`2mO8hlh zw5fhZH#;rA9v3u3#P+7&-Hy%XZ_JB{6^u(M{;RJ*8nwB&+(EELuW&`87SE7NXm>8R z9c;(*&884~_*+2p5WZa%y;2>-29s~R9!J%2X(iFxIQLS22V*nsZB#$KmWpTMmIj;71Do zG)bwRC-5xD#b{>e1i(slvjb$JxeUoakUwECiCU~VTLT0jh4VAl8gOe(>X22yjTpwV zlK@o;?^AZ{qd^!IWcr?B??b_zpsKhERuP){I(`+gO?{ge>Zsx_PQuW~3>&LaZST_- znb!YCZtYW)tI)ubc^p>&1)aG#F9SUod!`O?-d9+d1U^I5lRz(Pk2^*{!v13s&btmOa9xIka0(`K!k1v)^uv*n?;pYUqa*A8fVO~z;K_`IAikcM zB7!xwCdC-XAy3>6u&YEzznf(gs+g!10xH6##tO!c0&`hL2z07uFD1>aeMRVb!at>& zv$3zK>!x8=Kg{_Kw#389YgFR+GDm z9!-+~9yVrmr(_K%(a&k+zeN3{F*9I>MEy_c@`otD`T^T&@FJCr)xDt0oS#tkqIwe< zu{vd;oM0gdAR&=`C=ssPSMd$oxbW};-2>0UX&r=Y?%1FyM);VQzeS3Z^AN1LM*v{D zG_tbvj91RmvtA`j&v{j*X%bUv0|R)(y~vg10Wbl_7ccJkbo=!foST~+gkQqV;3nN` zI6H6y;7uc}K)b>t7Xty4$0ZlsE7V36vu%tc*m>aYQYR zeDau#yN(TBz-U&dY9jkSexY8#vz1J#K;4JuF$LJmV7M&kH}|V*$~|l5FfRg#i=D99 z?z$USrLMT*zXdknH>Lkq;IGN_4o|)uOh>BpJo_)G%jm)IJT?&6s7#~VV$ z?I*WVpnK(uIKrf5r98~R)yDL^YJoM%5Rl&#(O{E;_@^<`n!ro}+6=PeBWb;f>c39i zkll&WSTj@yRe^Tn5R;svlC9x*2CZ)BWD!UbVyC)RjEl)8m~L{5eYnm`mzObWqZ#>d zfm6SU+CtUo${^H&Fua<_Q5b~b-!XO>N;lM(gpd$0=#?jx)d{>qz0m?yti_A1_-ECQyh-jOUlKk@t#jOFNU9KnOF znHlB1V{oQtM>cFoUY~xeEx7*-V?JISi5&N@jZUxUdyH1{EEjeylOtA1Zf!yaJSQQG zgH}OD!Z}$m7K!%5vaGF*TYza1yQ2efFfS`SC#MZdq;|NtJb21D@dr0xz%xPG2+&Pm zUekwbx}Ehpck6Iy(ygaevm_!&dQs}#QCW~!q*4QeroUxL|)fpxnGeCiOY40XB{I{Y7hyzAN>g3E&NramAE zh(RNeP@CH#Frwmhr3YCUb)67dI@UxcOAdBuAV%pg%nrB#A!{-=F5VG`0PGX#Ok-A3 z(K1>E)Fo2w>q&>a$uxvJ95O@^I_J;F-0)l@Z=nywz0Grzs9mjPryM9^?Jb+ z%1Jc#J$0v~Tw;EbDs}V8P1-EADlugtR_pmiF5w!8<@jKOJk=^9FJ#U0VIn6!1S>;W z?1qc;p$LOc@x5#Wpm%BYOPP!(YS}+$zd!9;RxaaplV^>Vr=X}647%8^0}GxIVxs?t z2jcW03o13ilak(#Dp*Ebr(g%%flKpX#ADHAqR`ZVq#>D(hi}D<8|PNXaxd@>!^1W4 zIFnPA=-u^=CiXakfojoFkcl35AX(5t_lddCp>>y#0!-xSCYQ%jdgzVhM`@s_nGSJC-GMO9a4P^_EbOBwF{kLf`KcBh=tHZ}1s!Vz(0SC-_pl8^G zm~A5Hr;R`}HtUMiK>Y}p7Rf>SAqUO&fBp?OBTI2l$52)J0lV1u0Yz}&8<_VHy~JLA zPM-W6#-2MtKOfH14eaK6*ab2T>;s*aeV~Tk2co;&E!@gsKghad46CEuUU3)k&v&?E zbk4>}nyu2#yE<<)vuonKRqj2I6VLdmrOPDk{Su_XmiKvm#4jpGvwM|jZWl4AT{z*$ zhkc^s;L%%EulO^GKJ6X3hrK9?rIuDj0B~tasWlybF*R7(9AQ>rmN^;Zz86yY5x_2_ zx>g^aTges^;YjtkrArmcU=))&JTiS=pw2TPgqpaRRI7Rr_kH+St)LsI+I;*7lo$KL zP&tNc*@ec893@mi5H)5x{dSO0#EcrKAES@a|@yDE~n_1LXcJ}R2<6v{FKYAh;|To!SO^U{RkF(UOByo`O$ zpYT>3x*}= zK>~u|Stbx{XAq2y03HxS_~izE1alfC{-qZCEpVfTeBtsk?N7^k_;)J2gg}WU)=_i# z_=LpAanOn2QbAAcBr2#8tCw)mnFJ6p-zi5-m8q@~p>XJuCQ3*IKy4iCYCJp9sefgc zB_uSrP~5i?@1ShWyizk)vC)UqkE{6Wxb**$x`nrKo+r-n2V3}oG0?m0GqQ)vzrK(^ zl~SA&javvd<8NWOmi1Pl1i2_LHNy+T0r*?H1iC7`%kvykC^Hckd8*-iu2SD&=2c$g zIA;F4eT30uH$^#odKQ=1W^Rpg)C0A{hox6#pTq6Su}k$ic@_Y*x`FG-uB+^-N)%kS zShNrs8NJQz=~r4IFg+uR^Fs_>Z^%_wWxvwuw-igxsvt=DFPb?EIYZ zp@nm`)(4emtq*Oe4N^~4b2g4VSRYgiw#D~6Y*2Z>TEv}ce878o^4sc+zMqGYvd!ba xt*qL>)ai27_p2q=v0$G3pt5kZ>>P6*bxt}*@eF1T|NhrTZMsg@TK%7j`ajE-L8|}& literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/exceptions.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e634c6c0703d9e1f01c909f0595c87ebbf3be70e GIT binary patch literal 10132 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/formatting.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/formatting.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0b0003e00e7e1f3f0787d2e581a75c9311458be GIT binary patch literal 9412 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/globals.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/globals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74919270939e3298a5db05b025732c8fbb4650ef GIT binary patch literal 2424 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0a1513d29c235893b2900839e3b3496b824e0f6 GIT binary patch literal 16728 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/termui.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/termui.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc6dbbe865198d9864a9104e303fa916c653a545 GIT binary patch literal 25946 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/__pycache__/utils.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d35c047839644ec3f74ff06e1e6e3f0ba73c51e GIT binary patch literal 17574 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 U`GmfHQUAKs{8I+_WNjY(ADu)>wg3PC literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/click/_compat.py b/venv/Lib/site-packages/click/_compat.py new file mode 100644 index 0000000..766d286 --- /dev/null +++ b/venv/Lib/site-packages/click/_compat.py @@ -0,0 +1,626 @@ +import codecs +import io +import os +import re +import sys +import typing as t +from weakref import WeakKeyDictionary + +CYGWIN = sys.platform.startswith("cygwin") +MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version) +# Determine local App Engine environment, per Google's own suggestion +APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get( + "SERVER_SOFTWARE", "" +) +WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2 +auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") + + +def get_filesystem_encoding() -> str: + return sys.getfilesystemencoding() or sys.getdefaultencoding() + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding: str) -> bool: + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream: t.IO) -> str: + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) + + def __del__(self) -> None: + try: + self.detach() + except Exception: + pass + + def isatty(self) -> bool: + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream: + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + + if f is not None: + return t.cast(bytes, f(size)) + + return self._stream.read(size) + + def readable(self) -> bool: + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self) -> bool: + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.write("") # type: ignore + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +def _is_binary_reader(stream: t.IO, default: bool = False) -> bool: + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + +def _is_binary_writer(stream: t.IO, default: bool = False) -> bool: + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + +def _find_binary_reader(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + is_binary: t.Callable[[t.IO, bool], bool], + find_binary: t.Callable[[t.IO], t.Optional[t.BinaryIO]], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) + else: + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def _force_correct_text_reader( + text_reader: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: t.Union[str, os.PathLike, int], + mode: str, + encoding: t.Optional[str], + errors: t.Optional[str], +) -> t.IO: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, +) -> t.Tuple[t.IO, bool]: + binary = "b" in mode + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: t.Optional[int] = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO, af), True + + +class _AtomicFile: + def __init__(self, f: t.IO, tmp_filename: str, real_filename: str) -> None: + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self) -> str: + return self._real_filename + + def close(self, delete: bool = False) -> None: + if self.closed: + return + self._f.close() + os.replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._f, name) + + def __enter__(self) -> "_AtomicFile": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close(delete=exc_type is not None) + + def __repr__(self) -> str: + return repr(self._f) + + +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream: t.IO) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None +) -> bool: + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding() -> str: + import locale + + return locale.getpreferredencoding() + + _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi( + stream: t.TextIO, color: t.Optional[bool] = None + ) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + + if cached is not None: + return cached + + import colorama + + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s): + try: + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] + ) -> t.Optional[t.TextIO]: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO] +) -> t.Callable[[], t.TextIO]: + cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO: + stream = src_func() + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: t.Mapping[ + str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO] +] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/venv/Lib/site-packages/click/_termui_impl.py b/venv/Lib/site-packages/click/_termui_impl.py new file mode 100644 index 0000000..4b979bc --- /dev/null +++ b/venv/Lib/site-packages/click/_termui_impl.py @@ -0,0 +1,717 @@ +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" +import contextlib +import math +import os +import sys +import time +import typing as t +from gettext import gettext as _ + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +V = t.TypeVar("V") + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: t.Optional[t.Iterable[V]], + length: t.Optional[int] = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + label: t.Optional[str] = None, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label = label or "" + if file is None: + file = _default_text_stdout() + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width = width + self.autowidth = width == 0 + + if length is None: + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = t.cast(t.Iterable[V], range(length)) + self.iter = iter(iterable) + self.length = length + self.pos = 0 + self.avg: t.List[float] = [] + self.start = self.last_eta = time.time() + self.eta_known = False + self.finished = False + self.max_width: t.Optional[int] = None + self.entered = False + self.current_item: t.Optional[V] = None + self.is_hidden = not isatty(self.file) + self._last_line: t.Optional[str] = None + + def __enter__(self) -> "ProgressBar": + self.entered = True + self.render_progress() + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.render_finish() + + def __iter__(self) -> t.Iterator[V]: + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + def render_finish(self) -> None: + if self.is_hidden: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self) -> float: + if self.finished: + return 1.0 + return min(self.pos / (float(self.length or 1) or 1), 1.0) + + @property + def time_per_iteration(self) -> float: + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self) -> float: + if self.length is not None and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self) -> str: + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" + else: + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" + + def format_pos(self) -> str: + pos = str(self.pos) + if self.length is not None: + pos += f"/{self.length}" + return pos + + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] + + def format_bar(self) -> str: + if self.length is not None: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + chars = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) + return bar + + def format_progress_line(self) -> str: + show_percent = self.show_percent + + info_bits = [] + if self.length is not None and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self) -> None: + import shutil + + if self.is_hidden: + # Only output the label as it changes if the output is not a + # TTY. Use file=stderr if you expect to be piping stdout. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) + if new_width < old_width: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) # type: ignore + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps: int) -> None: + self.pos += n_steps + if self.length is not None and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length is not None + + def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. + + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False + self.current_item = None + self.finished = True + + def generator(self) -> t.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if self.is_hidden: + yield from self.iter + else: + for rv in self.iter: + self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + + yield rv + self.update(1) + + self.finish() + self.render_progress() + + +def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + pager_cmd = (os.environ.get("PAGER", None) or "").strip() + if pager_cmd: + if WIN: + return _tempfilepager(generator, pager_cmd, color) + return _pipepager(generator, pager_cmd, color) + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if WIN or sys.platform.startswith("os2"): + return _tempfilepager(generator, "more <", color) + if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: + return _pipepager(generator, "less", color) + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: + return _pipepager(generator, "more", color) + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + """ + import subprocess + + env = dict(os.environ) + + # If we're piping to less we might support colors under the + # condition that + cmd_detail = cmd.rsplit("/", 1)[-1].split() + if color is None and cmd_detail[0] == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}" + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) + stdin = t.cast(t.BinaryIO, c.stdin) + encoding = get_best_encoding(stdin) + try: + for text in generator: + if not color: + text = strip_ansi(text) + + stdin.write(text.encode(encoding, "replace")) + except (OSError, KeyboardInterrupt): + pass + else: + stdin.close() + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +def _tempfilepager( + generator: t.Iterable[str], cmd: str, color: t.Optional[bool] +) -> None: + """Page through text by invoking a program on a temporary file.""" + import tempfile + + fd, filename = tempfile.mkstemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, "wb")[0] as f: + f.write(text.encode(encoding)) + try: + os.system(f'{cmd} "{filename}"') + finally: + os.close(fd) + os.unlink(filename) + + +def _nullpager( + stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] +) -> None: + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor: + def __init__( + self, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self) -> str: + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + for editor in "sensible-editor", "vim", "nano": + if os.system(f"which {editor} >/dev/null 2>&1") == 0: + return editor + return "vi" + + def edit_file(self, filename: str) -> None: + import subprocess + + editor = self.get_editor() + environ: t.Optional[t.Dict[str, str]] = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + try: + c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True) + exit_code = c.wait() + if exit_code != 0: + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) + except OSError as e: + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e + + def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]: + import tempfile + + if not text: + data = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" + + if WIN: + data = text.replace("\n", "\r\n").encode("utf-8-sig") + else: + data = text.encode("utf-8") + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. + timestamp = os.path.getmtime(name) + + self.edit_file(name) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + with open(name, "rb") as f: + rv = f.read() + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore + finally: + os.unlink(name) + + +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: + import subprocess + + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url.replace('"', "")) + args = f'explorer /select,"{url}"' + else: + url = url.replace('"', "") + wait_str = "/WAIT" if wait else "" + args = f'start {wait_str} "" "{url}"' + return os.system(args) + elif CYGWIN: + if locate: + url = os.path.dirname(_unquote_file(url).replace('"', "")) + args = f'cygstart "{url}"' + else: + url = url.replace('"', "") + wait_str = "-w" if wait else "" + args = f'cygstart {wait_str} "{url}"' + return os.system(args) + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]: + if ch == "\x03": + raise KeyboardInterrupt() + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + return None + + +if WIN: + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> t.Iterator[int]: + yield -1 + + def getchar(echo: bool) -> str: + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + func: t.Callable[[], str] + + if echo: + func = msvcrt.getwche # type: ignore + else: + func = msvcrt.getwch # type: ignore + + rv = func() + + if rv in ("\x00", "\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + + _translate_ch_to_exc(rv) + return rv + +else: + import tty + import termios + + @contextlib.contextmanager + def raw_terminal() -> t.Iterator[int]: + f: t.Optional[t.TextIO] + fd: int + + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + + try: + old_settings = termios.tcgetattr(fd) + + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo: bool) -> str: + with raw_terminal() as fd: + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + + _translate_ch_to_exc(ch) + return ch diff --git a/venv/Lib/site-packages/click/_textwrap.py b/venv/Lib/site-packages/click/_textwrap.py new file mode 100644 index 0000000..b47dcbd --- /dev/null +++ b/venv/Lib/site-packages/click/_textwrap.py @@ -0,0 +1,49 @@ +import textwrap +import typing as t +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word( + self, + reversed_chunks: t.List[str], + cur_line: t.List[str], + cur_len: int, + width: int, + ) -> None: + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent: str) -> t.Iterator[None]: + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text: str) -> str: + rv = [] + + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + + if idx > 0: + indent = self.subsequent_indent + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/venv/Lib/site-packages/click/_winconsole.py b/venv/Lib/site-packages/click/_winconsole.py new file mode 100644 index 0000000..6b20df3 --- /dev/null +++ b/venv/Lib/site-packages/click/_winconsole.py @@ -0,0 +1,279 @@ +# This module is based on the excellent work by Adam BartoÅ¡ who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prompt. +import io +import sys +import time +import typing as t +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR + +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. + get_buffer = None +else: + + class Py_buffer(Structure): + _fields_ = [ + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + + def get_buffer(obj, writable=False): + buf = Py_buffer() + flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + return buffer_type.from_address(buf.buf) + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle): + self.handle = handle + + def isatty(self): + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self): + return True + + def readinto(self, b): + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError(f"Windows error: {GetLastError()}") + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self): + return True + + @staticmethod + def _get_error_message(errno): + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" + + def write(self, b): + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self) -> str: + return self.buffer.name + + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines: t.Iterable[t.AnyStr]) -> None: + for line in lines: + self.write(line) + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._text_stream, name) + + def isatty(self) -> bool: + return self.buffer.isatty() + + def __repr__(self): + return f"" + + +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> t.Optional[t.TextIO]: + if ( + get_buffer is not None + and encoding in {"utf-16-le", None} + and errors in {"strict", None} + and _is_console(f) + ): + func = _stream_factories.get(f.fileno()) + if func is not None: + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/venv/Lib/site-packages/click/core.py b/venv/Lib/site-packages/click/core.py new file mode 100644 index 0000000..a9a72c5 --- /dev/null +++ b/venv/Lib/site-packages/click/core.py @@ -0,0 +1,2995 @@ +import enum +import errno +import inspect +import os +import sys +import typing as t +from collections import abc +from contextlib import contextmanager +from contextlib import ExitStack +from functools import partial +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat + +from . import types +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _flag_needs_value +from .parser import OptionParser +from .parser import split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +if t.TYPE_CHECKING: + import typing_extensions as te + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +V = t.TypeVar("V") + + +def _complete_visible_commands( + ctx: "Context", incomplete: str +) -> t.Iterator[t.Tuple[str, "Command"]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. + + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. + """ + multi = t.cast(MultiCommand, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command + + +def _check_multicommand( + base_command: "MultiCommand", cmd_name: str, cmd: "Command", register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = ( + "It is not possible to add multi commands as children to" + " another multi command that is in chain mode." + ) + else: + hint = ( + "Found a multi command as subcommand to a multi command" + " that is in chain mode. This is not supported." + ) + raise RuntimeError( + f"{hint}. Command {base_command.name!r} is set to chain and" + f" {cmd_name!r} was added as a subcommand but it in itself is a" + f" multi command. ({cmd_name!r} is a {type(cmd).__name__}" + f" within a chained {type(base_command).__name__} named" + f" {base_command.name!r})." + ) + + +def batch(iterable: t.Iterable[V], batch_size: int) -> t.List[t.Tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size))) + + +@contextmanager +def augment_usage_errors( + ctx: "Context", param: t.Optional["Parameter"] = None +) -> t.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing( + invocation_order: t.Sequence["Parameter"], + declaration_order: t.Sequence["Parameter"], +) -> t.List["Parameter"]: + """Given a sequence of parameters in the order as should be considered + for processing and an iterable of parameters that exist, this returns + a list in the correct order as they should be processed. + """ + + def sort_key(item: "Parameter") -> t.Tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: t.Type["HelpFormatter"] = HelpFormatter + + def __init__( + self, + command: "Command", + parent: t.Optional["Context"] = None, + info_name: t.Optional[str] = None, + obj: t.Optional[t.Any] = None, + auto_envvar_prefix: t.Optional[str] = None, + default_map: t.Optional[t.Dict[str, t.Any]] = None, + terminal_width: t.Optional[int] = None, + max_content_width: t.Optional[int] = None, + resilient_parsing: bool = False, + allow_extra_args: t.Optional[bool] = None, + allow_interspersed_args: t.Optional[bool] = None, + ignore_unknown_options: t.Optional[bool] = None, + help_option_names: t.Optional[t.List[str]] = None, + token_normalize_func: t.Optional[t.Callable[[str], str]] = None, + color: t.Optional[bool] = None, + show_default: t.Optional[bool] = None, + ) -> None: + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: t.Dict[str, t.Any] = {} + #: the leftover arguments. + self.args: t.List[str] = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args: t.List[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj: t.Any = obj + self._meta: t.Dict[str, t.Any] = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + + self.default_map: t.Optional[t.Dict[str, t.Any]] = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`result_callback`. + self.invoked_subcommand: t.Optional[str] = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + + #: The width of the terminal (None is autodetection). + self.terminal_width: t.Optional[int] = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width: t.Optional[int] = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args: bool = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options: bool = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names: t.List[str] = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func: t.Optional[ + t.Callable[[str], str] + ] = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing: bool = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: t.Optional[str] = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color: t.Optional[bool] = color + + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: t.Optional[bool] = show_default + + self._close_callbacks: t.List[t.Callable[[], t.Any]] = [] + self._depth = 0 + self._parameter_source: t.Dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> "Context": + self._depth += 1 + push_context(self) + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @contextmanager + def scope(self, cleanup: bool = True) -> t.Iterator["Context"]: + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self) -> t.Dict[str, t.Any]: + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. + + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. + """ + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) + + def with_resource(self, context_manager: t.ContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._exit_stack.close() + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> "Context": + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: t.Type[V]) -> t.Optional[V]: + """Finds the closest object of a given type.""" + node: t.Optional["Context"] = self + + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + + node = node.parent + + return None + + def ensure_object(self, object_type: t.Type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[False]" = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + if self.default_map is not None: + value = self.default_map.get(name) + + if call and callable(value): + return value() + + return value + + return None + + def fail(self, message: str) -> "te.NoReturn": + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self) -> "te.NoReturn": + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> "te.NoReturn": + """Exits the application with a given exit code.""" + raise Exit(code) + + def get_usage(self) -> str: + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self) -> str: + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def _make_sub_context(self, command: "Command") -> "Context": + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + def invoke( + __self, # noqa: B902 + __callback: t.Union["Command", t.Callable[..., t.Any]], + *args: t.Any, + **kwargs: t.Any, + ) -> t.Any: + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + Note that before Click 3.2 keyword arguments were not properly filled + in against the intention of this code and no context was created. For + more information about this change and why it was done in a bugfix + release see :ref:`upgrade-to-3.2`. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + """ + if isinstance(__callback, Command): + other_cmd = __callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + __callback = other_cmd.callback + + ctx = __self._make_sub_context(other_cmd) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, param.get_default(ctx) + ) + + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = __self + + with augment_usage_errors(__self): + with ctx: + return __callback(*args, **kwargs) + + def forward( + __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902 + ) -> t.Any: + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. + """ + # Can only forward to other commands, not direct callbacks. + if not isinstance(__cmd, Command): + raise TypeError("Callback is not a command.") + + for param in __self.params: + if param not in kwargs: + kwargs[param] = __self.params[param] + + return __self.invoke(__cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> t.Optional[ParameterSource]: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) + + +class BaseCommand: + """The base command implements the minimal API contract of commands. + Most code will never use this as it does not implement a lot of useful + functionality but it can act as the direct subclass of alternative + parsing methods that do not depend on the Click parser. + + For instance, this can be used to bridge Click and other systems like + argparse or docopt. + + Because base commands do not implement a lot of the API that other + parts of Click take for granted, they are not supported for all + operations. For instance, they cannot be used with the decorators + usually and they have no built-in callback system. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: t.Type[Context] = Context + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + ) -> None: + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + + if context_settings is None: + context_settings = {} + + #: an optional dictionary with defaults passed to the context. + self.context_settings: t.Dict[str, t.Any] = context_settings + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire structure + below this command. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + :param ctx: A :class:`Context` representing this command. + + .. versionadded:: 8.0 + """ + return {"name": self.name} + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get usage") + + def get_help(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get help") + + def make_context( + self, + info_name: t.Optional[str], + args: t.List[str], + parent: t.Optional[Context] = None, + **extra: t.Any, + ) -> Context: + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it it's + the name of the command. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. + """ + for key, value in self.context_settings.items(): + if key not in extra: + extra[key] = value + + ctx = self.context_class( + self, info_name=info_name, parent=parent, **extra # type: ignore + ) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + """Given a context and a list of arguments this creates the parser + and parses the arguments, then modifies the context as necessary. + This is automatically invoked by :meth:`make_context`. + """ + raise NotImplementedError("Base commands do not know how to parse arguments.") + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the command. The default + implementation is raising a not implemented error. + """ + raise NotImplementedError("Base commands are not invokable by default") + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. Other + command classes will return more completions. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx.protected_args + ) + + return results + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: "te.Literal[True]" = True, + **extra: t.Any, + ) -> "te.NoReturn": + ... + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: + ... + + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt): + echo(file=sys.stderr) + raise Abort() from None + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo(_("Aborted!"), file=sys.stderr) + sys.exit(1) + + def _main_shell_completion( + self, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: t.Optional[str] = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + """ + if complete_var is None: + complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class Command(BaseCommand): + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + + :param deprecated: issues a message indicating that + the command is deprecated. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + callback: t.Optional[t.Callable[..., t.Any]] = None, + params: t.Optional[t.List["Parameter"]] = None, + help: t.Optional[str] = None, + epilog: t.Optional[str] = None, + short_help: t.Optional[str] = None, + options_metavar: t.Optional[str] = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + ) -> None: + super().__init__(name, context_settings) + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params: t.List["Parameter"] = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + info_dict.update( + params=[param.to_info_dict() for param in self.get_params(ctx)], + help=self.help, + epilog=self.epilog, + short_help=self.short_help, + hidden=self.hidden, + deprecated=self.deprecated, + ) + return info_dict + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx: Context) -> t.List["Parameter"]: + rv = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + rv = [*rv, help_option] + + return rv + + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] if self.options_metavar else [] + + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + + return rv + + def get_help_option_names(self, ctx: Context) -> t.List[str]: + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return list(all_names) + + def get_help_option(self, ctx: Context) -> t.Optional["Option"]: + """Returns the help option object.""" + help_options = self.get_help_option_names(ctx) + + if not help_options or not self.add_help_option: + return None + + def show_help(ctx: Context, param: "Parameter", value: str) -> None: + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + return Option( + help_options, + is_flag=True, + is_eager=True, + expose_value=False, + callback=show_help, + help=_("Show this message and exit."), + ) + + def make_parser(self, ctx: Context) -> OptionParser: + """Creates the underlying option parser for this command.""" + parser = OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help text to the formatter if it exists.""" + text = self.help if self.help is not None else "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + if text: + text = inspect.cleandoc(text).partition("\f")[0] + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + message = _( + "DeprecationWarning: The command {name!r} is deprecated." + ).format(name=self.name) + echo(style(message, fg="red"), err=True) + + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class MultiCommand(Command): + """A multi command is the basic implementation of a command that + dispatches to subcommands. The most common version is the + :class:`Group`. + + :param invoke_without_command: this controls how the multi command itself + is invoked. By default it's only invoked + if a subcommand is provided. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is enabled by default if + `invoke_without_command` is disabled or disabled + if it's enabled. If enabled this will add + ``--help`` as argument if no arguments are + passed. + :param subcommand_metavar: the string that is used in the documentation + to indicate the subcommand place. + :param chain: if this is set to `True` chaining of multiple subcommands + is enabled. This restricts the form of commands in that + they cannot have optional arguments but it allows + multiple commands to be chained together. + :param result_callback: The result callback to attach to this multi + command. This can be set or changed later with the + :meth:`result_callback` decorator. + """ + + allow_extra_args = True + allow_interspersed_args = False + + def __init__( + self, + name: t.Optional[str] = None, + invoke_without_command: bool = False, + no_args_is_help: t.Optional[bool] = None, + subcommand_metavar: t.Optional[str] = None, + chain: bool = False, + result_callback: t.Optional[t.Callable[..., t.Any]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + + if subcommand_metavar is None: + if chain: + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + else: + subcommand_metavar = "COMMAND [ARGS]..." + + self.subcommand_metavar = subcommand_metavar + self.chain = chain + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "Multi commands in chain mode cannot have" + " optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + rv = super().collect_usage_pieces(ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) + self.format_commands(ctx, formatter) + + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.result_callback() + def process_result(result, input): + return result + input + + :param replace: if set to `True` an already existing result + callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 + """ + + def decorator(f: F) -> F: + old_callback = self._result_callback + + if old_callback is None or replace: + self._result_callback = f + return f + + def function(__value, *args, **kwargs): # type: ignore + inner = old_callback(__value, *args, **kwargs) # type: ignore + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv + + return decorator + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section(_("Commands")): + formatter.write_dl(rows) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx.protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx.protected_args, *ctx.args] + ctx.args = [] + ctx.protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: t.List[str] + ) -> t.Tuple[t.Optional[str], t.Optional[Command], t.List[str]]: + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if split_opt(cmd_name)[0]: + self.parse_args(ctx, ctx.args) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + """Given a context and a command name, this returns a + :class:`Command` object if it exists or returns `None`. + """ + raise NotImplementedError + + def list_commands(self, ctx: Context) -> t.List[str]: + """Returns a list of subcommand names in the order they should + appear. + """ + return [] + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class Group(MultiCommand): + """A group allows a command to have subcommands attached. This is + the most common way to implement nesting in Click. + + :param name: The name of the group command. + :param commands: A dict mapping names to :class:`Command` objects. + Can also be a list of :class:`Command`, which will use + :attr:`Command.name` to create the dict. + :param attrs: Other command arguments described in + :class:`MultiCommand`, :class:`Command`, and + :class:`BaseCommand`. + + .. versionchanged:: 8.0 + The ``commmands`` argument can be a list of command objects. + """ + + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: t.Optional[t.Type[Command]] = None + + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: t.Optional[t.Union[t.Type["Group"], t.Type[type]]] = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: t.Optional[str] = None, + commands: t.Optional[t.Union[t.Dict[str, Command], t.Sequence[Command]]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: t.Dict[str, Command] = commands + + def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_multicommand(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: + ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: + ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'command(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> "Group": + ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: + ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'group(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> "Group": + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> t.List[str]: + return sorted(self.commands) + + +class CommandCollection(MultiCommand): + """A command collection is a multi command that merges multiple multi + commands together into one. This is a straightforward implementation + that accepts a list of different multi commands as sources and + provides all the commands for each of them. + """ + + def __init__( + self, + name: t.Optional[str] = None, + sources: t.Optional[t.List[MultiCommand]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + #: The list of registered multi commands. + self.sources: t.List[MultiCommand] = sources or [] + + def add_source(self, multi_cmd: MultiCommand) -> None: + """Adds a new multi command to the chain dispatcher.""" + self.sources.append(multi_cmd) + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> t.List[str]: + rv: t.Set[str] = set() + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: t.Any) -> t.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The later is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: a string or list of strings that are environment variables + that should be checked. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + + param_type_name = "parameter" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + required: bool = False, + default: t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]] = None, + callback: t.Optional[t.Callable[[Context, "Parameter", t.Any], t.Any]] = None, + nargs: t.Optional[int] = None, + multiple: bool = False, + metavar: t.Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: t.Optional[t.Union[str, t.Sequence[str]]] = None, + shell_complete: t.Optional[ + t.Callable[ + [Context, "Parameter", str], + t.Union[t.List["CompletionItem"], t.List[str]], + ] + ] = None, + ) -> None: + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + # Skip no default or callable default. + check_default = default if not callable(default) else None + + if check_default is not None: + if multiple: + try: + # Only check the first value against nargs. + check_default = next(_check_iter(check_default), None) + except TypeError: + raise ValueError( + "'default' must be a list when 'multiple' is true." + ) from None + + # Can be None for multiple with empty default. + if nargs != 1 and check_default is not None: + try: + _check_iter(check_default) + except TypeError: + if multiple: + message = ( + "'default' must be a list of lists when 'multiple' is" + " true and 'nargs' != 1." + ) + else: + message = "'default' must be a list when 'nargs' != 1." + + raise ValueError(message) from None + + if nargs > 1 and len(check_default) != nargs: + subject = "item length" if multiple else "length" + raise ValueError( + f"'default' {subject} must match nargs={nargs}." + ) + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self.default, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + raise NotImplementedError() + + @property + def human_readable_name(self) -> str: + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name # type: ignore + + def make_metavar(self) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(self) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) # type: ignore + + if value is None: + value = self.default + + if call and callable(value): + value = value() + + return value + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, t.Any] + ) -> t.Tuple[t.Any, ParameterSource]: + value = opts.get(self.name) # type: ignore + source = ParameterSource.COMMANDLINE + + if value is None: + value = self.value_from_envvar(ctx) + source = ParameterSource.ENVIRONMENT + + if value is None: + value = ctx.lookup_default(self.name) # type: ignore + source = ParameterSource.DEFAULT_MAP + + if value is None: + value = self.get_default(ctx) + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the option's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value is None: + return () if self.multiple or self.nargs == -1 else None + + def check_iter(value: t.Any) -> t.Iterator: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None + + if self.nargs == 1 or self.type.is_composite: + convert: t.Callable[[t.Any], t.Any] = partial( + self.type, param=self, ctx=ctx + ) + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Tuple: + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Tuple: + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: + if value is None: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + value = self.callback(ctx, self, value) + + return value + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + if self.envvar is None: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: t.Mapping[str, t.Any], args: t.List[str] + ) -> t.Tuple[t.Any, t.List[str]]: + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + ctx.set_parameter_source(self.name, source) # type: ignore + + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + + value = None + + if self.expose_value: + ctx.params[self.name] = value # type: ignore + + return value, args + + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: + pass + + def get_usage_pieces(self, ctx: Context) -> t.List[str]: + return [] + + def get_error_hint(self, ctx: Context) -> str: + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast(t.List["CompletionItem"], results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page. Normally, environment variables are not + shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + + .. versionchanged:: 8.1.0 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1.0 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1.0 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + show_default: t.Union[bool, str, None] = None, + prompt: t.Union[bool, str] = False, + confirmation_prompt: t.Union[bool, str] = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: t.Optional[bool] = None, + flag_value: t.Optional[t.Any] = None, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + help: t.Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + default_is_missing = "default" not in attrs + super().__init__(param_decls, type=type, multiple=multiple, **attrs) + + if prompt is True: + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: t.Optional[str] = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # If prompt is enabled but not required, then the option can be + # used as a flag to indicate using prompt or flag_value. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + if is_flag is None: + if flag_value is not None: + # Implicitly a flag because flag_value was set. + is_flag = True + elif self._flag_needs_value: + # Not a flag, but when used as a flag it shows a prompt. + is_flag = False + else: + # Implicitly a flag because flag options were given. + is_flag = bool(self.secondary_opts) + elif is_flag is False and not self._flag_needs_value: + # Not a flag, and prompt is not enabled, can be used as a + # flag if flag_value is set. + self._flag_needs_value = flag_value is not None + + if is_flag and default_is_missing and not self.required: + self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False + + if flag_value is None: + flag_value = not self.default + + if is_flag and type is None: + # Re-guess the type from the flag value instead of the + # default. + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = is_flag + self.is_bool_flag = is_flag and isinstance(self.type, types.BoolParamType) + self.flag_value: t.Any = flag_value + + # Counting + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + if __debug__: + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError("'prompt' is not valid for non-boolean flag.") + + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if decl.isidentifier(): + if name is not None: + raise TypeError(f"Name '{name}' defined twice") + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) + else: + possible_names.append(split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError("Could not determine name for option") + + if not opts and not secondary_opts: + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + action = f"{action}_const" + + if self.is_bool_flag and self.secondary_opts: + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) + + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: t.Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar()}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + extra = [] + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + var_str = ( + envvar + if isinstance(envvar, str) + else ", ".join(str(d) for d in envvar) + ) + extra.append(_("env var: {var}").format(var=var_str)) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or (show_default and (default_value is not None)): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = split_opt( + (self.opts if self.default else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + else: + default_string = str(default_value) + + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra.append(range_str) + + if self.required: + extra.append(_("required")) + + if extra: + extra_str = "; ".join(extra) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + # If we're a non boolean flag our default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return param.flag_value # type: ignore + + return None + + return super().get_default(ctx, call=call) + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + return prompt( + self.prompt, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + ) + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is None: + return None + + value_depth = (self.nargs != 1) + bool(self.multiple) + + if value_depth > 0: + rv = self.type.split_envvar_value(rv) + + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + + return rv + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, "Parameter"] + ) -> t.Tuple[t.Any, ParameterSource]: + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option can be + # given as a flag without a value. This is different from None + # to distinguish from the flag not being given at all. + if value is _flag_needs_value: + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + elif ( + self.multiple + and value is not None + and any(v is _flag_needs_value for v in value) + ): + value = [self.flag_value if v is _flag_needs_value else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt if + # prompting is enabled. + elif ( + source in {None, ParameterSource.DEFAULT} + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the parameter constructor. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: t.Sequence[str], + required: t.Optional[bool] = None, + **attrs: t.Any, + ) -> None: + if required is None: + if attrs.get("default") is not None: + required = False + else: + required = attrs.get("nargs", 1) > 0 + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + if __debug__: + if self.default is not None and self.nargs == -1: + raise TypeError("'default' is not supported for nargs=-1.") + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() # type: ignore + + def make_metavar(self) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(self) + if not var: + var = self.name.upper() # type: ignore + if not self.required: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Could not determine name for argument") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}." + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> t.List[str]: + return [self.make_metavar()] + + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar()}'" + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) diff --git a/venv/Lib/site-packages/click/decorators.py b/venv/Lib/site-packages/click/decorators.py new file mode 100644 index 0000000..ef1b1a5 --- /dev/null +++ b/venv/Lib/site-packages/click/decorators.py @@ -0,0 +1,497 @@ +import inspect +import types +import typing as t +from functools import update_wrapper +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .globals import get_current_context +from .utils import echo + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command]) + + +def pass_context(f: F) -> F: + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args, **kwargs): # type: ignore + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + +def pass_obj(f: F) -> F: + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args, **kwargs): # type: ignore + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + +def make_pass_decorator( + object_type: t.Type, ensure: bool = False +) -> "t.Callable[[F], F]": + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore + ctx = get_current_context() + + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + return decorator + + +def pass_meta_key( + key: str, *, doc_description: t.Optional[str] = None +) -> "t.Callable[[F], F]": + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +@t.overload +def command( + __func: t.Callable[..., t.Any], +) -> Command: + ... + + +@t.overload +def command( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[..., Command]: + ... + + +@t.overload +def command( + name: t.Optional[str] = None, + cls: t.Type[CmdType] = ..., + **attrs: t.Any, +) -> t.Callable[..., CmdType]: + ... + + +def command( + name: t.Union[str, t.Callable, None] = None, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> t.Union[Command, t.Callable[..., Command]]: + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function with + underscores replaced by dashes. If you want to change that, you can + pass the intended name as the first argument. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: the name of the command. This defaults to the function + name with underscores replaced by dashes. + :param cls: the command class to instantiate. This defaults to + :class:`Command`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Optional[t.Callable] = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = Command + + def decorator(f: t.Callable[..., t.Any]) -> Command: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + cmd = cls( # type: ignore[misc] + name=name or f.__name__.lower().replace("_", "-"), # type: ignore[arg-type] + callback=f, + params=params, + **attrs, + ) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +@t.overload +def group( + __func: t.Callable, +) -> Group: + ... + + +@t.overload +def group( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[[F], Group]: + ... + + +def group( + name: t.Union[str, t.Callable, None] = None, **attrs: t.Any +) -> t.Union[Group, t.Callable[[F], Group]]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if attrs.get("cls") is None: + attrs["cls"] = Group + + if callable(name): + grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs)) + return grp(name) + + return t.cast(Group, command(name, **attrs)) + + +def _param_memo(f: FC, param: Parameter) -> None: + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore + + +def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + """ + + def decorator(f: FC) -> FC: + ArgumentClass = attrs.pop("cls", None) or Argument + _param_memo(f, ArgumentClass(param_decls, **attrs)) + return f + + return decorator + + +def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + """ + + def decorator(f: FC) -> FC: + # Issue 926, copy attrs, so pre-defined options can re-use the same cls= + option_attrs = attrs.copy() + OptionClass = option_attrs.pop("cls", None) or Option + _param_memo(f, OptionClass(param_decls, **option_attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. + + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) + + +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. + + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) + + +def version_option( + version: t.Optional[str] = None, + *param_decls: str, + package_name: t.Optional[str] = None, + prog_name: t.Optional[str] = None, + message: t.Optional[str] = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. + + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. On Python < 3.8, the ``importlib_metadata`` + backport must be installed. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. + """ + if message is None: + message = _("%(prog)s, version %(version)s") + + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + metadata: t.Optional[types.ModuleType] + + try: + from importlib import metadata # type: ignore + except ImportError: + # Python < 3.8 + import importlib_metadata as metadata # type: ignore + + try: + version = metadata.version(package_name) # type: ignore + except metadata.PackageNotFoundError: # type: ignore + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + t.cast(str, message) + % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) + + +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--help`` option which immediately prints the help page + and exits the program. + + This is usually unnecessary, as the ``--help`` option is added to + each command automatically unless ``add_help_option=False`` is + passed. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) diff --git a/venv/Lib/site-packages/click/exceptions.py b/venv/Lib/site-packages/click/exceptions.py new file mode 100644 index 0000000..9e20b3e --- /dev/null +++ b/venv/Lib/site-packages/click/exceptions.py @@ -0,0 +1,287 @@ +import os +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .utils import echo + +if t.TYPE_CHECKING: + from .core import Context + from .core import Parameter + + +def _join_param_hints( + param_hint: t.Optional[t.Union[t.Sequence[str], str]] +) -> t.Optional[str]: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) + + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception. + exit_code = 1 + + def __init__(self, message: str) -> None: + super().__init__(message) + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.Optional[t.IO] = None) -> None: + if file is None: + file = get_text_stderr() + + echo(_("Error: {message}").format(message=self.format_message()), file=file) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code = 2 + + def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd = self.ctx.command if self.ctx else None + + def show(self, file: t.Optional[t.IO] = None) -> None: + if file is None: + file = get_text_stderr() + color = None + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" + if self.ctx is not None: + color = self.ctx.color + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__( + self, + message: str, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + ) -> None: + super().__init__(message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + return _("Invalid value: {message}").format(message=self.message) + + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__( + self, + message: t.Optional[str] = None, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + param_type: t.Optional[str] = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) + self.param_type = param_type + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint: t.Optional[str] = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + param_hint = None + + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += f". {msg_extra}" + else: + msg = msg_extra + + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__( + self, + option_name: str, + message: t.Optional[str] = None, + possibilities: t.Optional[t.Sequence[str]] = None, + ctx: t.Optional["Context"] = None, + ) -> None: + if message is None: + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__( + self, option_name: str, message: str, ctx: t.Optional["Context"] = None + ) -> None: + super().__init__(message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename: str, hint: t.Optional[str] = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename = os.fsdecode(filename) + self.filename = filename + + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: + self.exit_code = code diff --git a/venv/Lib/site-packages/click/formatting.py b/venv/Lib/site-packages/click/formatting.py new file mode 100644 index 0000000..ddd2a2f --- /dev/null +++ b/venv/Lib/site-packages/click/formatting.py @@ -0,0 +1,301 @@ +import typing as t +from contextlib import contextmanager +from gettext import gettext as _ + +from ._compat import term_len +from .parser import split_opt + +# Can force a width. This is used by the test system +FORCED_WIDTH: t.Optional[int] = None + + +def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]: + widths: t.Dict[int, int] = {} + + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows( + rows: t.Iterable[t.Tuple[str, str]], col_count: int +) -> t.Iterator[t.Tuple[str, ...]]: + for row in rows: + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p: t.List[t.Tuple[int, bool, str]] = [] + buf: t.List[str] = [] + indent = None + + def _flush_par() -> None: + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter: + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__( + self, + indent_increment: int = 2, + width: t.Optional[int] = None, + max_width: t.Optional[int] = None, + ) -> None: + import shutil + + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer: t.List[str] = [] + + def write(self, string: str) -> None: + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self) -> None: + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self) -> None: + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage( + self, prog: str, args: str = "", prefix: t.Optional[str] = None + ) -> None: + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. + """ + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading: str) -> None: + """Writes a heading into the buffer.""" + self.write(f"{'':>{self.current_indent}}{heading}:\n") + + def write_paragraph(self) -> None: + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text: str) -> None: + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl( + self, + rows: t.Sequence[t.Tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write(f"{'':>{self.current_indent}}{first}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + @contextmanager + def section(self, name: str) -> t.Iterator[None]: + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self) -> t.Iterator[None]: + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self) -> str: + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]: + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + + for opt in options: + prefix = split_opt(opt)[0] + + if prefix == "/": + any_prefix_is_slash = True + + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/venv/Lib/site-packages/click/globals.py b/venv/Lib/site-packages/click/globals.py new file mode 100644 index 0000000..480058f --- /dev/null +++ b/venv/Lib/site-packages/click/globals.py @@ -0,0 +1,68 @@ +import typing as t +from threading import local + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": + ... + + +@t.overload +def get_current_context(silent: bool = ...) -> t.Optional["Context"]: + ... + + +def get_current_context(silent: bool = False) -> t.Optional["Context"]: + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: + if not silent: + raise RuntimeError("There is no active click context.") from e + + return None + + +def push_context(ctx: "Context") -> None: + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context() -> None: + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]: + """Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + + ctx = get_current_context(silent=True) + + if ctx is not None: + return ctx.color + + return None diff --git a/venv/Lib/site-packages/click/parser.py b/venv/Lib/site-packages/click/parser.py new file mode 100644 index 0000000..2d5a2ed --- /dev/null +++ b/venv/Lib/site-packages/click/parser.py @@ -0,0 +1,529 @@ +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + +# Sentinel value that indicates an option was passed as a flag without a +# value but is not a flag option. Option.consume_value uses this to +# prompt or use the flag_value. +_flag_needs_value = object() + + +def _unpack_args( + args: t.Sequence[str], nargs_spec: t.Sequence[int] +) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] + spos: t.Optional[int] = None + + def _fetch(c: "te.Deque[V]") -> t.Optional[V]: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def split_opt(opt: str) -> t.Tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = split_opt(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" + + +def split_arg_string(string: str) -> t.List[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out + + +class Option: + def __init__( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes = set() + + for opt in opts: + prefix, value = split_opt(opt) + if not prefix: + raise ValueError(f"Invalid start character for option ({opt})") + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self) -> bool: + return self.action in ("store", "append") + + def process(self, value: str, state: "ParsingState") -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore + else: + raise ValueError(f"unknown action '{self.action}'") + state.order.append(self.obj) + + +class Argument: + def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], + state: "ParsingState", + ) -> None: + if self.nargs > 1: + assert value is not None + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + if self.nargs == -1 and self.obj.envvar is not None and value == (): + # Replace empty tuple with None so that a value from the + # environment may be tried. + value = None + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class ParsingState: + def __init__(self, rargs: t.List[str]) -> None: + self.opts: t.Dict[str, t.Any] = {} + self.largs: t.List[str] = [] + self.rargs = rargs + self.order: t.List["CoreParameter"] = [] + + +class OptionParser: + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + """ + + def __init__(self, ctx: t.Optional["Context"] = None) -> None: + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options = False + + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + + self._short_opt: t.Dict[str, Option] = {} + self._long_opt: t.Dict[str, Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: t.List[Argument] = [] + + def add_option( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ) -> None: + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``append_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + opts = [normalize_opt(opt, self.ctx) for opt in opts] + option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument( + self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 + ) -> None: + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + self._args.append(Argument(obj, dest=dest, nargs=nargs)) + + def parse_args( + self, args: t.List[str] + ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state: ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state: ParsingState) -> None: + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt( + self, opt: str, explicit_value: t.Optional[str], state: ParsingState + ) -> None: + if opt not in self._long_opt: + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + value = self._get_value_from_state(opt, option, state) + + elif explicit_value is not None: + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) + + else: + value = None + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we re-combinate the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(f"{prefix}{''.join(unknown_options)}") + + def _get_value_from_state( + self, option_name: str, option: Option, state: ParsingState + ) -> t.Any: + nargs = option.nargs + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = _flag_needs_value + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = _flag_needs_value + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) diff --git a/venv/Lib/site-packages/click/py.typed b/venv/Lib/site-packages/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/click/shell_completion.py b/venv/Lib/site-packages/click/shell_completion.py new file mode 100644 index 0000000..c17a8e6 --- /dev/null +++ b/venv/Lib/site-packages/click/shell_completion.py @@ -0,0 +1,580 @@ +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import BaseCommand +from .core import Context +from .core import MultiCommand +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .parser import split_arg_string +from .utils import echo + + +def shell_complete( + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: t.Optional[str] = None, + **kwargs: t.Any, + ) -> None: + self.value = value + self.type = type + self.help = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +compdef %(complete_func)s %(prog_name)s; +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response; + + for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + set response $response $value; + end; + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> t.Dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions( + self, args: t.List[str], incomplete: str + ) -> t.List[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + def _check_version(self) -> None: + import subprocess + + output = subprocess.run( + ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + raise RuntimeError( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ) + ) + else: + raise RuntimeError( + _("Couldn't detect Bash version, shell completion is not supported.") + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +_available_shells: t.Dict[str, t.Type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: t.Type[ShellComplete], name: t.Optional[str] = None +) -> None: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + +def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + value = ctx.params[param.name] + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: BaseCommand, ctx_args: t.Dict[str, t.Any], prog_name: str, args: t.List[str] +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + ctx = cli.make_context(prog_name, args.copy(), **ctx_args) + args = ctx.protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, MultiCommand): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) + args = ctx.protected_args + ctx.args + else: + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + sub_ctx = cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx.protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: t.List[str], incomplete: str +) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/venv/Lib/site-packages/click/termui.py b/venv/Lib/site-packages/click/termui.py new file mode 100644 index 0000000..bfb2f5a --- /dev/null +++ b/venv/Lib/site-packages/click/termui.py @@ -0,0 +1,787 @@ +import inspect +import io +import itertools +import os +import sys +import typing as t +from gettext import gettext as _ + +from ._compat import isatty +from ._compat import strip_ansi +from ._compat import WIN +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile + +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func: t.Callable[[str], str] = input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +def hidden_prompt_func(prompt: str) -> str: + import getpass + + return getpass.getpass(prompt) + + +def _build_prompt( + text: str, + suffix: str, + show_default: bool = False, + default: t.Optional[t.Any] = None, + show_choices: bool = True, + type: t.Optional[ParamType] = None, +) -> str: + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += f" ({', '.join(map(str, type.choices))})" + if default is not None and show_default: + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" + + +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name # type: ignore + + return default + + +def prompt( + text: str, + default: t.Optional[t.Any] = None, + hide_input: bool = False, + confirmation_prompt: t.Union[bool, str] = False, + type: t.Optional[t.Union[ParamType, t.Any]] = None, + value_proc: t.Optional[t.Callable[[str], t.Any]] = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + value = default + break + try: + result = value_proc(value) + except UsageError as e: + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306 + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: t.Optional[bool] = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def echo_via_pager( + text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str], + color: t.Optional[bool] = None, +) -> None: + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast(t.Iterable[str], text_or_generator)) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, str) else str(el) for el in i) + + from ._termui_impl import pager + + return pager(itertools.chain(text_generator, "\n"), color) + + +def progressbar( + iterable: t.Optional[t.Iterable[V]] = None, + length: t.Optional[int] = None, + label: t.Optional[str] = None, + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, +) -> "ProgressBar[V]": + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: + + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: The file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + Added the ``update_min_steps`` parameter. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. Added the ``update`` method to + the object. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) + + +def clear() -> None: + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + if WIN: + os.system("cls") + else: + sys.stdout.write("\033[2J\033[1;1H") + + +def _interpret_color( + color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0 +) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bold: t.Optional[bool] = None, + dim: t.Optional[bool] = None, + underline: t.Optional[bool] = None, + overline: t.Optional[bool] = None, + italic: t.Optional[bool] = None, + blink: t.Optional[bool] = None, + reverse: t.Optional[bool] = None, + strikethrough: t.Optional[bool] = None, + reset: bool = True, +) -> str: + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + If the terminal supports it, color may also be specified as: + + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param strikethrough: if provided this will enable or disable + striking through text. + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 + """ + if not isinstance(text, str): + text = str(text) + + bits = [] + + if fg: + try: + bits.append(f"\033[{_interpret_color(fg)}m") + except KeyError: + raise TypeError(f"Unknown color {fg!r}") from None + + if bg: + try: + bits.append(f"\033[{_interpret_color(bg, 10)}m") + except KeyError: + raise TypeError(f"Unknown color {bg!r}") from None + + if bold is not None: + bits.append(f"\033[{1 if bold else 22}m") + if dim is not None: + bits.append(f"\033[{2 if dim else 22}m") + if underline is not None: + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") + if blink is not None: + bits.append(f"\033[{5 if blink else 25}m") + if reverse is not None: + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text: str) -> str: + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.AnyStr]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, + **styles: t.Any, +) -> None: + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + + .. versionadded:: 2.0 + """ + if message is not None and not isinstance(message, (bytes, bytearray)): + message = style(message, **styles) + + return echo(message, file=file, nl=nl, err=err, color=color) + + +def edit( + text: t.Optional[t.AnyStr] = None, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + filename: t.Optional[str] = None, +) -> t.Optional[t.AnyStr]: + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. + """ + from ._termui_impl import Editor + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + + if filename is None: + return ed.edit(text) + + ed.edit_file(filename) + return None + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar: t.Optional[t.Callable[[bool], str]] = None + + +def getchar(echo: bool = False) -> str: + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + global _getchar + + if _getchar is None: + from ._termui_impl import getchar as f + + _getchar = f + + return _getchar(echo) + + +def raw_terminal() -> t.ContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: t.Optional[str] = None, err: bool = False) -> None: + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + + if info is None: + info = _("Press any key to continue...") + + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/venv/Lib/site-packages/click/testing.py b/venv/Lib/site-packages/click/testing.py new file mode 100644 index 0000000..e395c2e --- /dev/null +++ b/venv/Lib/site-packages/click/testing.py @@ -0,0 +1,479 @@ +import contextlib +import io +import os +import shlex +import shutil +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from .core import BaseCommand + + +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: + self._input = input + self._output = output + self._paused = False + + def __getattr__(self, x: str) -> t.Any: + return getattr(self._input, x) + + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + + return rv + + def read(self, n: int = -1) -> bytes: + return self._echo(self._input.read(n)) + + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: + return self._echo(self._input.readline(n)) + + def readlines(self) -> t.List[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> t.Iterator[bytes]: + return iter(self._echo(x) for x in self._input) + + def __repr__(self) -> str: + return repr(self._input) + + +@contextlib.contextmanager +def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class _NamedTextIOWrapper(io.TextIOWrapper): + def __init__( + self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: t.Optional[t.Union[str, bytes, t.IO]], charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast(t.IO, input)) + + if rv is not None: + return rv + + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif isinstance(input, str): + input = input.encode(charset) + + return io.BytesIO(t.cast(bytes, input)) + + +class Result: + """Holds the captured result of an invoked CLI script.""" + + def __init__( + self, + runner: "CliRunner", + stdout_bytes: bytes, + stderr_bytes: t.Optional[bytes], + return_value: t.Any, + exit_code: int, + exception: t.Optional[BaseException], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = None, + ): + #: The runner that created the result + self.runner = runner + #: The standard output as bytes. + self.stdout_bytes = stdout_bytes + #: The standard error as bytes, or None if not available + self.stderr_bytes = stderr_bytes + #: The value returned from the invoked command. + #: + #: .. versionadded:: 8.0 + self.return_value = return_value + #: The exit code as integer. + self.exit_code = exit_code + #: The exception that happened if one did. + self.exception = exception + #: The traceback + self.exc_info = exc_info + + @property + def output(self) -> str: + """The (standard) output as unicode string.""" + return self.stdout + + @property + def stdout(self) -> str: + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self) -> str: + """The standard error as unicode string.""" + if self.stderr_bytes is None: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from stdin writes + to stdout. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param mix_stderr: if this is set to `False`, then stdout and stderr are + preserved as independent streams. This is useful for + Unix-philosophy apps that have predictable stdout and + noisy stderr, such that each may be measured + independently + """ + + def __init__( + self, + charset: str = "utf-8", + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + echo_stdin: bool = False, + mix_stderr: bool = True, + ) -> None: + self.charset = charset + self.env = env or {} + self.echo_stdin = echo_stdin + self.mix_stderr = mix_stderr + + def get_default_prog_name(self, cli: "BaseCommand") -> str: + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env( + self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None + ) -> t.Mapping[str, t.Optional[str]]: + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation( + self, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + color: bool = False, + ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up stdin with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + :param input: the input stream to put into sys.stdin. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionchanged:: 8.0 + ``stderr`` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + """ + bytes_input = make_input_stream(input, self.charset) + echo_input = None + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + bytes_output = io.BytesIO() + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, bytes_output) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + bytes_output, encoding=self.charset, name="", mode="w" + ) + + bytes_error = None + if self.mix_stderr: + sys.stderr = sys.stdout + else: + bytes_error = io.BytesIO() + sys.stderr = _NamedTextIOWrapper( + bytes_error, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(prompt or "") + val = text_input.readline().rstrip("\r\n") + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + return text_input.readline().rstrip("\r\n") + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None + ) -> bool: + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + + old_env = {} + try: + for key, value in env.items(): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (bytes_output, bytes_error) + finally: + for key, value in old_env.items(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + formatting.FORCED_WIDTH = old_forced_width + + def invoke( + self, + cli: "BaseCommand", + args: t.Optional[t.Union[str, t.Sequence[str]]] = None, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + catch_exceptions: bool = True, + color: bool = False, + **extra: t.Any, + ) -> Result: + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. + """ + exc_info = None + with self.isolation(input=input, env=env, color=color) as outstreams: + return_value = None + exception: t.Optional[BaseException] = None + exit_code = 0 + + if isinstance(args, str): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code) + + if e_code is None: + e_code = 0 + + if e_code != 0: + exception = e + + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + stdout = outstreams[0].getvalue() + if self.mix_stderr: + stderr = None + else: + stderr = outstreams[1].getvalue() # type: ignore + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: t.Optional[t.Union[str, os.PathLike]] = None + ) -> t.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var] + os.chdir(dt) + + try: + yield t.cast(str, dt) + finally: + os.chdir(cwd) + + if temp_dir is None: + try: + shutil.rmtree(dt) + except OSError: # noqa: B014 + pass diff --git a/venv/Lib/site-packages/click/types.py b/venv/Lib/site-packages/click/types.py new file mode 100644 index 0000000..b45ee53 --- /dev/null +++ b/venv/Lib/site-packages/click/types.py @@ -0,0 +1,1073 @@ +import os +import stat +import typing as t +from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import _get_argv_encoding +from ._compat import get_filesystem_encoding +from ._compat import open_stream +from .exceptions import BadParameter +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem + + +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. + + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. + """ + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 + + #: the descriptive name of this type + name: str + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter: t.ClassVar[t.Optional[str]] = None + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> t.Any: + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param: "Parameter") -> t.Optional[str]: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: "Parameter") -> t.Optional[str]: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. + """ + return value + + def split_envvar_value(self, rv: str) -> t.Sequence[str]: + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail( + self, + message: str, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> "t.NoReturn": + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self) -> int: # type: ignore + raise NotImplementedError() + + +class FuncParamType(ParamType): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: + self.name = func.__name__ + self.func = func + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + return self.func(value) + except ValueError: + try: + value = str(value) + except UnicodeError: + value = value.decode("utf-8", "replace") + + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = get_filesystem_encoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") + return value + return str(value) + + def __repr__(self) -> str: + return "STRING" + + +class Choice(ParamType): + """The choice type allows a value to be checked against a fixed set + of supported values. All of these values have to be strings. + + You should only pass a list or tuple of choices. Other iterables + (like generators) may lead to surprising results. + + The resulting value will always be one of the originally passed choices + regardless of ``case_sensitive`` or any ``ctx.token_normalize_func`` + being specified. + + See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + """ + + name = "choice" + + def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None: + self.choices = choices + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + choices_str = "|".join(self.choices) + + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: "Parameter") -> str: + return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices)) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + # Match through normalization and case sensitivity + # first do token_normalize_func, then lowercase + # preserve original `value` to produce an accurate message in + # `self.fail` + normed_value = value + normed_choices = {choice: choice for choice in self.choices} + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(value) + normed_choices = { + ctx.token_normalize_func(normed_choice): original + for normed_choice, original in normed_choices.items() + } + + if not self.case_sensitive: + normed_value = normed_value.casefold() + normed_choices = { + normed_choice.casefold(): original + for normed_choice, original in normed_choices.items() + } + + if normed_value in normed_choices: + return normed_choices[normed_value] + + choices_str = ", ".join(map(repr, self.choices)) + self.fail( + ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + def __init__(self, formats: t.Optional[t.Sequence[str]] = None): + self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"] + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, datetime): + return value + + for format in self.formats: + converted = self._try_to_convert_date(value, format) + + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) + self.fail( + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return "DateTime" + + +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[t.Type] + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) + + +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + self.min = min + self.max = max + self.min_open = min_open + self.max_open = max_open + self.clamp = clamp + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + + if self.clamp: + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + + return rv + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" + + +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int + + def __repr__(self) -> str: + return "INT" + + +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "integer range" + + def _clamp( # type: ignore + self, bound: int, dir: "te.Literal[1, -1]", open: bool + ) -> int: + if not open: + return bound + + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + if not open: + return bound + + # Could use Python 3.9's math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") + + +class BoolParamType(ParamType): + name = "boolean" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if value in {False, True}: + return bool(value) + + norm = value.strip().lower() + + if norm in {"1", "true", "t", "yes", "y", "on"}: + return True + + if norm in {"0", "false", "f", "no", "n", "off"}: + return False + + self.fail( + _("{value!r} is not a valid boolean.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import uuid + + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Starting with Click 2.0, files can also be opened atomically in which + case all writes go into a separate file in the same folder and upon + completion the file will be moved over to the original location. This + is useful if a file regularly read by other users is modified. + + See :ref:`file-args` for more information. + """ + + name = "filename" + envvar_list_splitter = os.path.pathsep + + def __init__( + self, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: t.Optional[bool] = None, + atomic: bool = False, + ) -> None: + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: t.Any) -> bool: + if self.lazy is not None: + return self.lazy + if value == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + if hasattr(value, "read") or hasattr(value, "write"): + return value + + lazy = self.resolve_lazy_flag(value) + + if lazy: + f: t.IO = t.cast( + t.IO, + LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ), + ) + + if ctx is not None: + ctx.call_on_close(f.close_intelligently) # type: ignore + + return f + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + + return f + except OSError as e: # noqa: B014 + self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] + + +class Path(ParamType): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter = os.path.pathsep + + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: t.Optional[t.Type] = None, + executable: bool = False, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result(self, rv: t.Any) -> t.Any: + if self.type is not None and not isinstance(rv, self.type): + if self.type is str: + rv = os.fsdecode(rv) + elif self.type is bytes: + rv = os.fsencode(rv) + else: + rv = self.type(rv) + + return rv + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + # os.path.realpath doesn't resolve symlinks on Windows + # until Python 3.8. Use pathlib for now. + import pathlib + + rv = os.fsdecode(pathlib.Path(rv).resolve()) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + _("{name} '{filename}' is a directory.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types: t.Sequence[t.Union[t.Type, ParamType]]) -> None: + self.types = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict + + @property + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore + return len(self.types) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) + + +def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. + """ + guessed_type = False + + if ty is None and default is not None: + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) + else: + ty = type(default) + + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + + if isinstance(ty, ParamType): + return ty + + if ty is str or ty is None: + return STRING + + if ty is int: + return INT + + if ty is float: + return FLOAT + + if ty is bool: + return BOOL + + if guessed_type: + return STRING + + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) + except TypeError: + # ty is an instance (correct), so issubclass fails. + pass + + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() diff --git a/venv/Lib/site-packages/click/utils.py b/venv/Lib/site-packages/click/utils.py new file mode 100644 index 0000000..8283788 --- /dev/null +++ b/venv/Lib/site-packages/click/utils.py @@ -0,0 +1,580 @@ +import os +import re +import sys +import typing as t +from functools import update_wrapper +from types import ModuleType + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import get_filesystem_encoding +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN +from .globals import resolve_color_default + +if t.TYPE_CHECKING: + import typing_extensions as te + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def safecall(func: F) -> F: + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args, **kwargs): # type: ignore + try: + return func(*args, **kwargs) + except Exception: + pass + + return update_wrapper(t.cast(F, wrapper), func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(get_filesystem_encoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return str(value) + + +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string.""" + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. + words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + + total_length = 0 + last_index = len(words) - 1 + + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate + break + + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." + + +class LazyFile: + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__( + self, + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, + ): + self.name = filename + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + self._f: t.Optional[t.IO] + + if filename == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self.open(), name) + + def __repr__(self) -> str: + if self._f is not None: + return repr(self._f) + return f"" + + def open(self) -> t.IO: + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: # noqa: E402 + from .exceptions import FileError + + raise FileError(self.name, hint=e.strerror) from e + self._f = rv + return rv + + def close(self) -> None: + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self) -> None: + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self) -> "LazyFile": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close_intelligently() + + def __iter__(self) -> t.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class KeepOpenFile: + def __init__(self, file: t.IO) -> None: + self._file = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> "KeepOpenFile": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> t.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.Any]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. + + Compared to :func:`print`, this does the following: + + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. + + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. + + .. versionchanged:: 6.0 + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: t.Optional[t.Union[str, bytes]] = str(message) + else: + out = message + + if nl: + out = out or "" + if isinstance(out, str): + out += "\n" + else: + out += b"\n" + + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): + binary_file = _find_binary_writer(file) + + if binary_file is not None: + file.flush() + binary_file.write(out) + binary_file.flush() + return + + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: + color = resolve_color_default(color) + + if should_strip_ansi(file, color): + out = strip_ansi(out) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file) # type: ignore + elif not color: + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO: + """Returns a system stream for byte processing. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener() + + +def get_text_stream( + name: "te.Literal['stdin', 'stdout', 'stderr']", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", +) -> t.TextIO: + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener(encoding, errors) + + +def open_file( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic)) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + + if not should_close: + f = t.cast(t.IO, KeepOpenFile(f)) + + return f + + +def format_filename( + filename: t.Union[str, bytes, os.PathLike], shorten: bool = False +) -> str: + """Formats a filename for user display. The main purpose of this + function is to ensure that the filename can be displayed at all. This + will decode the filename to unicode if necessary in a way that it will + not fail. Optionally, it can shorten the filename to not include the + full path to the filename. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + + return os.fsdecode(filename) + + +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Windows (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Windows (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no affect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class PacifyFlushWrapper: + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped: t.IO) -> None: + self.wrapped = wrapped + + def flush(self) -> None: + try: + self.wrapped.flush() + except OSError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr: str) -> t.Any: + return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + if getattr(_main, "__package__", None) is None or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: t.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> t.List[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/venv/Lib/site-packages/colorama-0.4.4.dist-info/INSTALLER b/venv/Lib/site-packages/colorama-0.4.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/colorama-0.4.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/colorama-0.4.4.dist-info/LICENSE.txt b/venv/Lib/site-packages/colorama-0.4.4.dist-info/LICENSE.txt new file mode 100644 index 0000000..3105888 --- /dev/null +++ b/venv/Lib/site-packages/colorama-0.4.4.dist-info/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2010 Jonathan Hartley +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 holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/venv/Lib/site-packages/colorama-0.4.4.dist-info/METADATA b/venv/Lib/site-packages/colorama-0.4.4.dist-info/METADATA new file mode 100644 index 0000000..2a175c2 --- /dev/null +++ b/venv/Lib/site-packages/colorama-0.4.4.dist-info/METADATA @@ -0,0 +1,415 @@ +Metadata-Version: 2.1 +Name: colorama +Version: 0.4.4 +Summary: Cross-platform colored terminal text. +Home-page: https://github.com/tartley/colorama +Author: Jonathan Hartley +Author-email: tartley@tartley.com +Maintainer: Arnon Yaari +License: BSD +Keywords: color colour terminal text ansi windows crossplatform xplatform +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 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 :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Terminals +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* + +.. image:: https://img.shields.io/pypi/v/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Supported Python versions + +.. image:: https://travis-ci.org/tartley/colorama.svg?branch=master + :target: https://travis-ci.org/tartley/colorama + :alt: Build Status + +Colorama +======== + +Makes ANSI escape character sequences (for producing colored terminal text and +cursor positioning) work under MS Windows. + +.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama¤cy_code=USD + :alt: Donate with Paypal + +`PyPI for releases `_ · +`Github for source `_ · +`Colorama for enterprise on Tidelift `_ + +If you find Colorama useful, please |donate| to the authors. Thank you! + + +Installation +------------ + +.. code-block:: bash + + pip install colorama + # or + conda install -c anaconda colorama + + +Description +----------- + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which +would appear as gobbledygook in the output), and converting them into the +appropriate win32 calls to modify the state of the terminal. On other platforms, +Colorama does nothing. + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.init()``. + +An alternative approach is to install ``ansi.sys`` on Windows machines, which +provides the same behaviour for all applications running in terminals. Colorama +is intended for situations where that isn't easy (e.g., maybe your app doesn't +have an installer.) + +Demo scripts in the source code repository print some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screenshots show that, on Windows, Colorama does not support ANSI 'dim +text'; it looks the same as 'normal text'. + +Usage +----- + +Initialisation +.............. + +Applications should initialise Colorama using: + +.. code-block:: python + + from colorama import init + init() + +On Windows, calling ``init()`` will filter ANSI escape sequences out of any +text sent to ``stdout`` or ``stderr``, and replace them with equivalent Win32 +calls. + +On other platforms, calling ``init()`` has no effect (unless you request other +optional functionality; see "Init Keyword Args", below). By design, this permits +applications to call ``init()`` unconditionally on all platforms, after which +ANSI output should just work. + +To stop using Colorama before your program exits, simply call ``deinit()``. +This will restore ``stdout`` and ``stderr`` to their original values, so that +Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is +cheaper than calling ``init()`` again (but does the same thing). + + +Colored Output +.............. + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences: + +.. code-block:: python + + from colorama import Fore, Back, Style + print(Fore.RED + 'some red text') + print(Back.GREEN + 'and with a green background') + print(Style.DIM + 'and in dim text') + print(Style.RESET_ALL) + print('back to normal now') + +...or simply by manually printing ANSI sequences from your own code: + +.. code-block:: python + + print('\033[31m' + 'some red text') + print('\033[39m') # and reset to default color + +...or, Colorama can be used in conjunction with existing ANSI libraries +such as the venerable `Termcolor `_ +or the fabulous `Blessings `_. +This is highly recommended for anything more than trivial coloring: + +.. code-block:: python + + from colorama import init + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + init() + + # then use Termcolor for all colored text output + print(colored('Hello, World!', 'green', 'on_red')) + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will +perform this reset automatically on program exit. + + +Cursor Positioning +.................. + +ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for +an example of how to generate them. + + +Init Keyword Args +................. + +``init()`` accepts some ``**kwargs`` to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that: + + .. code-block:: python + + from colorama import init + init(autoreset=True) + print(Fore.RED + 'some red text') + print('automatically back to default color again') + +init(strip=None): + Pass ``True`` or ``False`` to override whether ANSI codes should be + stripped from the output. The default behaviour is to strip if on Windows + or if output is redirected (not a tty). + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ANSI codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the ``.write()`` method to do their work. + If this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or + ``strip`` or ``convert`` are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly: + + .. code-block:: python + + import sys + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) + + +Recognised ANSI Sequences +......................... + +ANSI sequences generally take the form:: + + ESC [ ; ... + +Where ```` is an integer, and ```` is a single letter. Zero or +more params are passed to a ````. If no params are passed, it is +generally synonymous with passing a single zero. No spaces exist in the +sequence; they have been inserted here simply to read more easily. + +The only ANSI sequences that Colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ y;x H # position cursor at x across, y down + ESC [ y;x f # position cursor at x across, y down + ESC [ n A # move cursor n lines up + ESC [ n B # move cursor n lines down + ESC [ n C # move cursor n characters forward + ESC [ n D # move cursor n characters backward + + # clear the screen + ESC [ mode J # clear the screen + + # clear the line + ESC [ mode K # clear the line + +Multiple numeric params to the ``'m'`` command can be combined into a single +sequence:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised or stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the Issues on +GitHub. + + +Status & Known Problems +----------------------- + +I've personally only tested it on Windows XP (CMD, Console2), Ubuntu +(gnome-terminal, xterm), and OS X. + +Some presumably valid ANSI sequences aren't recognised (see details below), +but to my knowledge nobody has yet complained about this. Puzzling. + +See outstanding issues and wish-list: +https://github.com/tartley/colorama/issues + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd love to hear about it on that issues list, would be delighted by patches, +and would be happy to grant commit access to anyone who submits a working patch +or two. + + +License +------- + +Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see +LICENSE file. + + +Development +----------- + +Help and fixes welcome! + +Tested on CPython 2.7, 3.5, 3.6, 3.7 and 3.8. + +No requirements other than the standard library. +Development requirements are captured in requirements-dev.txt. + +To create and populate a virtual environment:: + + ./bootstrap.ps1 # Windows + make bootstrap # Linux + +To run tests:: + + ./test.ps1 # Windows + make test # Linux + +If you use nose to run the tests, you must pass the ``-s`` flag; otherwise, +``nosetests`` applies its own proxy to ``stdout``, which confuses the unit +tests. + + +Professional support +-------------------- + +.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png + :alt: Tidelift + :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for colorama 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-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + + +Thanks +------ + +* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5. +* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``, + providing a solution to issue #7's setuptools/distutils debate, + and other fixes. +* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``. +* Matthew McCormick for politely pointing out a longstanding crash on non-Win. +* Ben Hoyt, for a magnificent fix under 64-bit Windows. +* Jesse at Empty Square for submitting a fix for examples in the README. +* User 'jamessp', an observant documentation fix for cursor positioning. +* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 + fix. +* Julien Stuyck, for wisely suggesting Python3 compatible updates to README. +* Daniel Griffith for multiple fabulous patches. +* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty + output. +* Roger Binns, for many suggestions, valuable feedback, & bug reports. +* Tim Golden for thought and much appreciated feedback on the initial idea. +* User 'Zearin' for updates to the README file. +* John Szakmeister for adding support for light colors +* Charles Merriam for adding documentation to demos +* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes +* Florian Bruhin for a fix when stdout or stderr are None +* Thomas Weininger for fixing ValueError on Windows +* Remi Rampin for better Github integration and fixes to the README file +* Simeon Visser for closing a file handle using 'with' and updating classifiers + to include Python 3.3 and 3.4 +* Andy Neff for fixing RESET of LIGHT_EX colors. +* Jonathan Hartley for the initial idea and implementation. + + + diff --git a/venv/Lib/site-packages/colorama-0.4.4.dist-info/RECORD b/venv/Lib/site-packages/colorama-0.4.4.dist-info/RECORD new file mode 100644 index 0000000..3516c65 --- /dev/null +++ b/venv/Lib/site-packages/colorama-0.4.4.dist-info/RECORD @@ -0,0 +1,18 @@ +colorama-0.4.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +colorama-0.4.4.dist-info/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491 +colorama-0.4.4.dist-info/METADATA,sha256=JmU7ePpEh1xcqZV0JKcrrlU7cp5o4InDlHJXbo_FTQw,14551 +colorama-0.4.4.dist-info/RECORD,, +colorama-0.4.4.dist-info/WHEEL,sha256=gxPaqcqKPLUXaSAKwmfHO7_iAOlVvmp33DewnUluBB8,116 +colorama-0.4.4.dist-info/top_level.txt,sha256=_Kx6-Cni2BT1PEATPhrSRxo0d7kSgfBbHf5o7IF1ABw,9 +colorama/__init__.py,sha256=pCdErryzLSzDW5P-rRPBlPLqbBtIRNJB6cMgoeJns5k,239 +colorama/__pycache__/__init__.cpython-39.pyc,, +colorama/__pycache__/ansi.cpython-39.pyc,, +colorama/__pycache__/ansitowin32.cpython-39.pyc,, +colorama/__pycache__/initialise.cpython-39.pyc,, +colorama/__pycache__/win32.cpython-39.pyc,, +colorama/__pycache__/winterm.cpython-39.pyc,, +colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522 +colorama/ansitowin32.py,sha256=yV7CEmCb19MjnJKODZEEvMH_fnbJhwnpzo4sxZuGXmA,10517 +colorama/initialise.py,sha256=PprovDNxMTrvoNHFcL2NZjpH2XzDc8BLxLxiErfUl4k,1915 +colorama/win32.py,sha256=bJ8Il9jwaBN5BJ8bmN6FoYZ1QYuMKv2j8fGrXh7TJjw,5404 +colorama/winterm.py,sha256=2y_2b7Zsv34feAsP67mLOVc-Bgq51mdYGo571VprlrM,6438 diff --git a/venv/Lib/site-packages/colorama-0.4.4.dist-info/WHEEL b/venv/Lib/site-packages/colorama-0.4.4.dist-info/WHEEL new file mode 100644 index 0000000..6d38aa0 --- /dev/null +++ b/venv/Lib/site-packages/colorama-0.4.4.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.35.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/Lib/site-packages/colorama-0.4.4.dist-info/top_level.txt b/venv/Lib/site-packages/colorama-0.4.4.dist-info/top_level.txt new file mode 100644 index 0000000..3fcfb51 --- /dev/null +++ b/venv/Lib/site-packages/colorama-0.4.4.dist-info/top_level.txt @@ -0,0 +1 @@ +colorama diff --git a/venv/Lib/site-packages/colorama/__init__.py b/venv/Lib/site-packages/colorama/__init__.py new file mode 100644 index 0000000..b149ed7 --- /dev/null +++ b/venv/Lib/site-packages/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.4' diff --git a/venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f649dfdf8c49d33a4defe9d2aee2756eb9e1bc2d GIT binary patch literal 427 zcmYk2u};G<5QgK#Nz8YILs!#gA=Se$I1u4QFlVP3CqbmDEL8`(YOD#K})#Bjo!Ivx38p{=Dop|k_ z?9N!>@hxAy_;lXwTj8_m#yVpwFY-3h(?zY1nH)Oo*jwrkgN9A4a`Jv|z z;LUKvc0V5~^gi1&bltVHPE}d6k^J+(sog-Cfjoz}*f{<5;&{5`* z!c*|U`(GM&`s)GXLI0IA@L9h(a`$$a9|rva*fn6y(TH2#V2iQ-OXk1qx4o@?;Dzkg zuH!xje$bajIy=sS;|E@K_qE8Od^;T3=$aslds6`v!XX)yGX@}U!T{X(fie$<92>B& z-o9G_3s!m!q19V$upkk7;gCrKVlY64NCzymC8q%7SZ5CA5$ZUj_}@MEGsiVY;4+B) z8GTuI$Q*75E@#XyO{m2ACA2-1+Bn8>+B7p(HS~OTbwbfBDuQw9LR%MM1juCyRF(pj z1@#5hq)`o$LKo>8#Hs-{1*i6j)+S*z@be!5LI}knX)2pH-;e|Hj!^O!35-xZP!7nU z`bG^koQ3*lya4eCUP13v+^k+ck&veMMBOq#Yb)U>iRW94Yp%F~=xpn0oE3vg5ytrDHFuFw+H z(iLH#5AKgRi?G5Yv@0qjpj{DeDMFt_xZw!XH$qz@3*iYuA7K;WHv}8ucLWEZb2K~U zDv_wSA>KmkwwfZb)NQpoBKf%0Zm(i zKY)GT!wAqx2GOT)?erUt>;Iqd*5MB%x(VtdOws5Y1kBUuI|NMDsEV+FfF?y0fgwCa z7$9sTcnHrBo&$7v3-Da;Yye{k#((t=uD{qj*aR+4en{kGd{Xzq*YLZ83Cr^U(HsH{ zrBg{wCt|YR#at3vbNP`-c2>HNYHg90X=c~j?Q^X{^D)=uu{w@9$G6x#Q04eh5-HoJ zBiFV${>5{wb)G>;BjC>#$KM~0KOZ7F+Iq&^P#&JL56Eoc=oBek=vbmqAP`BBkXDLh pLpJR5_9={?;$!@t3KD`cfS!=)s2OmZnyD8v#awX??qrhS{SQ5>Z=(PJ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-39.pyc b/venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9db0a5583ede56b735f89012f61883ef9e8c07cd GIT binary patch literal 7678 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 OQ<%4Gj9qJf<^KS!wfq(U literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-39.pyc b/venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0efe42a149f3a70020f89a842da0da29f4e8788a GIT binary patch literal 1694 zcma)6OK%%D5GJ_~tyT{|(x$DE7D5gI7D$t#0eT3UqD4|4ib9vDF(7Oq&>}0Zv)UDg zlvSt7rS_qh_BYr-FZoM*?J0kumvn|3*;0T2<$^;FXO668cbDN7h;lS8f36J4C7KW2xi z$*lf-CdbdwZqjj?mmsI|epX~QlX+(Jz1b;*c*HJtLm)FAaknxUJC&BO(VYC;rT)Ol z(ldLOE!mPU#l$<~uxGi#>;2BHIQFEEGLRw7@$wA5QcO0An>MmO{ek~^XMxYG7+tN5AB=lXkq5-QJ}nkuaEhXAkV6b*2l8B)4*&`hgU zrdp(<0$NTtjP6fq^{<&7sj|>jSSwWodXgE7c0x1ptEQaW(j~lVVC*LvY!%>+*{t$u z_iF+`L~;3)yN_^YK10F8E{}qcha%>o*L@SMM&IW#hfmPs{oL%K^({BM#Lbbglq?Rn ztcRLU@B$@12^=QS!a1}_bV+nbT=BD>xfnyGDar?EH?%;U3W-;HINyke>V$SEv75Un zh*BAn>TmuRCi%s-V$ZNz4ybZ)ETVe+!qrb5n{c;uTPLFOG0m9C=L;Iwf^%_oRs}G^ z%-Dx?rVCGWx!{q2PjO`B_$?+=n4xPj&>MygW| z^6b#TRJ9~Nr?y-)=RP1wL}%k_d#HJ9aHh$5h9I k=<8{z=DGfca_j0SLUu-??P1u$buS9yTTuh6D2f{Y0sE+59{>OV literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/colorama/__pycache__/win32.cpython-39.pyc b/venv/Lib/site-packages/colorama/__pycache__/win32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df52efe5c7cd547a966e0d1e91a1da1c0ef9bb1e GIT binary patch literal 3926 zcmai1TT>g!6`r1(MnYil6=T+xaoDxC9M-Xu+RB#8Wn17&MJrqghwKzvQ-iuCBug{6 zXJiZ|KjGJ;@|IWcYN zt@#Uv@ek^Ze|dBspd^21hQSTr;LKC3+&tz^_!GUNU+hi#lf5Z_ z3ah)FGye1wgBSS3JA+S%;(_JQ2&Z$lbIv~x?u8Z;=YxxAT|zypIhXx8-JcRO2drg^ zInBRIHs<&!aALt8SF9w*Pf_c-a0Y$WLVDTA8WIYI{qD za<3YPTu2OfyX`35+Yw2FM2 z&E2pSiOuD>(eH^UO*UJh3|nG3j-}sjZ3(gYT12lmvHxb$PQ|Zx!p2MRlg&oljb+#i zzk()r?=0=?Deqo4>}~Pz{uH+P8O4y~ng3ryNvdYbxucfW)jL|`3^gbU%C>U0_M~X4 zd_9%@M%tG`xs9N)9ZHpZzWS`TzOr^=OH49o*#{`eeGp;zj2nmx=3Cqpb`x=;sSSU0 z(Q{DeP`fnRa(s9{_h6qq&%-_iHCI`!)mN)4LA|oJvQi5ky?Fe1Wi5DCd%U`TF40<* z+EEF>G~#G!dr7%`x8B|tYMSp>`Z9^-^B7>Z<48^HK1@^D-s-0!QC=c9+7XX;)j7WV zZTP0$>-Vw&n%yAF9(~jRCsEM|v+g69#HVTfWX--p5(>!QF%N06i zbK)YrrEJ#r7j7={TmSQ~T%M5A*j2Ib6#J8$K~tV3a*hVA^>|0wRnbh9`zTJ+xTl=8 zc56G8=fQ^e+KK2kkG)ZnnN={M1)*OsM4``a66szUwSht+DZwo+NgQq8vL z@+6S+5E#?<*f+^9k&=ys(D0aTy3A!Be(nc5Cy9H)Y_t(MyEYpwni>bjTb9CXsX1T= z?7)0$rq;kbV0A->McLM?s4Kw}kyhd;iMyiSkU~U{`pu@0&!T25C$N&Tx8k^4wv@Xa zM!YMO-AK0DDwp&d4Ur^c+H16n0o*g(X$Nq;bQALveaAq1lX-`>DZj*61Led=2iLlj zky1DvH{Tg4OQA2Qb-aJ5^r|oebP|ph5f5z=UKYfjT>P%xxX|LMBcE%XirTNj`fs3 z%|LSN>&vSz))mz&Yina0jckkAs16PWR0e$r2WH()_>~ScC(7TT%x8 ziAcF1ucMNb1|&JQBxvPrB9sp0B_gv#$j0jO7;YzTgR+|e=j|L_4{pdi{UlX0SuJ=l zVws8jMQCS_p*^|!D{P)n0tU9`d1l)ek**4CnoXO)nl8qMMxG?_<%UJO!y|Nt?qI_m z*n9Jb%&D#S_Q3psQPO&EbB30aIsTJI4&8W;8+ z0k5$_OJn7xHn4IuR?fgBtO%w3u=I&iqv*O>+8zRPt3(-oY3v1YtAqze9MNk5Og!Ou zc2(Reecm7dlZ_0BQaQ7Oo7sV&mkUH5fZ(b*efKOc9idJEE|+QI3K7asDo=y@K_eg2 z;75@^clz3YhIn@I!yy6o5N`PgjGxG{6m^ujv}@9BmVtegOPQJ(3$gWf+7%mkbT}!s zKGx^Vakyy7zhSvVr$r`fXqVY~`O7Sre}gWbn)=x-NkqALoh?_Z%Eb-AyWPx(r$(k) zkusKQ;#qGemZ|ROl%_8Y4Wy*3EGY~mJ$RHGWbYB3Pw4TWyq7{oqI>s_DvsY_@AsRrxzKGdokX%ij~{$|v?1i?oL%M)1q0 zBs={PRvWTP!@277%0_*CP2br%G&FK!NNXR^&kpZi_9VMcsU~>?Vj~OT$)V#5xd^X4 z4;g9Jbj|6ro@pPtmS>`7?tff6YuTfgdECN0tn$Il4P{RDvsGNrF6KPDaMmk$h5rJP C?toDM literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-39.pyc b/venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f58be6490cad47c237265b54247b2f2319b8907b GIT binary patch literal 4648 zcmcgwUw0eD6`$FETCHSTb__Nl1VbPoT0#qLp(zF1vSU&lnd3jD)-h*~tXVq}vNoMv z#l&ab({q|v=vT1cntqf%^aIRmpZpbg;&*3vB};L5tbFF~%-lP(_s{R%nRRnw!ejV- z(+l=*zrxu6XmI&wVX%l65fM!AE<554&e<9hhA^KpVFt!O%!Y}vC2WjsJ+?4*L;>T1 z9^1m*V%6dW$2L{2?BjOtqYqmcjQohZh!*`4Bw!6LSi=y!VG5&R3A14ftKkT{Q4mhU z6@^AoxD8Jfv9qV#uiL#^*bU_c)-p+3T^K}6B4o#fO~fHm0I63?%JuzT^C<9r<@x?m zDEeLMmwo@Q{bo1g*vsonwJ(*myuP!l?6u9+)w;53yGwOtZmzDV!h@x?)%y05a&}kO z*Ehaa7DRTRG5Ipyp~;WiwYz&eQ6Qtehte^)2|(UW*KC9yLFJ-{L?X-Ffsxl3ip>Q~`0hHS(vqFZS;| zmR#*$_|n$C+=`!c1F8MyLi*zGAoZ#vOL)pMkqHpx)HgOCEUhbNdGr3-7u#w&efrDr z+1lpDPJLzQh_p9HYe|7=w1_N7mDh96sZ3TblHd;@LzRW`lmQl|u>PX3+D*H8G2oCGoa+NxY1ivbZhg#5{Tv z;vI2ayo%nWcvsvIuc0?3Zi?3-Ul9d3s;UYA-8SOUBDm#0>XX_vwCFRCn4R&Mb&PY~ zF%#xn2|KeB*0Ii!M|e7zE8ywkS;W)Bvm`iZSuniDsOvCPCnVunU0)00TG)%iZm`vo zLC{<79~=boe(xYu&erzI#?H1X9WTYPZ14BuAOgrmaM0{`V^xfT*l&UpDce5?WuPW< zUOKe>eL(KgtR6akL|{;p1L1V+_}%v5!}u9jRups(E(MxmOv%;mwPW9>U`GTQ#w)zc zUHY?6Dw*%@X)%B`gGgH#&oN*}D8LvkqW`5veg==)}-Gu3MiniPhXez|;;lq=DzS}7Q{uXC-7VGp1Yfs|vK ztD+3Fe%&sam+4;~(SIOuuVq0d|51P7{dElBbJk~a5_+>!_#8U(#E|bL%mO@Ovtw-_ zWWLdx8O}_x2YL=`?8MBh1%#d%^(vPiVOIp^Z9LNGX#5PL^C9uAG%%J2eDh>kUwMm12r z4x%dJxYn0ZD8CAETWNy{3FS@k22nvC8|snHfQcYGz(GLoTOSXo11f4 z?8T8Hd$LIBGZUvR*HOAb=Pshe&dkeEl6SEHUf0GfpfCRbqUMM38MQ1A&=QI2WFYIt zZMvqbp?1l@v~M7nN6Vy0>1e&0n|H;Ek!nCE1zlS;9+%P4mH~0HqAjDlgqh$bacU=a z2eHD@Xrr?6Id)*2I*@S^D{%x)r|-=ap;fbf4<+4cicUX@tA#NqNP3H0B=R{CdNYK` z!-{{W_>)WalRMOOx=|noHjn#dqLun26+FbE%p<8cJU+`$UdlZ(BCAiFk+2uBn(mC+ zA^$~3JA@K4bx0O!IK)b820$?dfQ4_QUX=+-SpXIJGEFPD)89@=H)c{5R#^7gAl%nG_BC<||&Z;cI)Tf}+#<3~WGqTv>fUa<7yJ|KXaQspBORX^&W?4I z`4_f*?#54*SaYj5!BHabrNlt7%o|5nSES){dhB~xzK^}rytzfa-w>gTprq@Pr2B|I z#M3IfLc{w+=meC1CbCOp=xN>4pUgMGpixBM39jLmUDLHa+biME@m$ySJkRwOyjLVi r;!|CvR}kfd`<wgL8k8CY))jO2?Y5Gr)&nOJ~ZeF%3rHcI%?8C7Q literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/colorama/ansi.py b/venv/Lib/site-packages/colorama/ansi.py new file mode 100644 index 0000000..11ec695 --- /dev/null +++ b/venv/Lib/site-packages/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 = '\a' + + +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/site-packages/colorama/ansitowin32.py b/venv/Lib/site-packages/colorama/ansitowin32.py new file mode 100644 index 0000000..6039a05 --- /dev/null +++ b/venv/Lib/site-packages/colorama/ansitowin32.py @@ -0,0 +1,258 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import re +import sys +import os + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL +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\\]([^\a]*)(\a)\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 == BEL: + if paramstring.count(";") == 1: + 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/site-packages/colorama/initialise.py b/venv/Lib/site-packages/colorama/initialise.py new file mode 100644 index 0000000..430d066 --- /dev/null +++ b/venv/Lib/site-packages/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/site-packages/colorama/win32.py b/venv/Lib/site-packages/colorama/win32.py new file mode 100644 index 0000000..c2d8360 --- /dev/null +++ b/venv/Lib/site-packages/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/site-packages/colorama/winterm.py b/venv/Lib/site-packages/colorama/winterm.py new file mode 100644 index 0000000..0fdb4ec --- /dev/null +++ b/venv/Lib/site-packages/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/site-packages/docopt-0.6.2.dist-info/INSTALLER b/venv/Lib/site-packages/docopt-0.6.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/docopt-0.6.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT b/venv/Lib/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT new file mode 100644 index 0000000..3b2eb5c --- /dev/null +++ b/venv/Lib/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2012 Vladimir Keleshev, + +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/site-packages/docopt-0.6.2.dist-info/METADATA b/venv/Lib/site-packages/docopt-0.6.2.dist-info/METADATA new file mode 100644 index 0000000..b3d0ef8 --- /dev/null +++ b/venv/Lib/site-packages/docopt-0.6.2.dist-info/METADATA @@ -0,0 +1,470 @@ +Metadata-Version: 2.1 +Name: docopt +Version: 0.6.2 +Summary: Pythonic argument parser, that will make you smile +Home-page: http://docopt.org +Author: Vladimir Keleshev +Author-email: vladimir@keleshev.com +License: MIT +Keywords: option arguments parsing optparse argparse getopt +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Topic :: Utilities +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: License :: OSI Approved :: MIT License +License-File: LICENSE-MIT + +``docopt`` creates *beautiful* command-line interfaces +====================================================================== + +Video introduction to **docopt**: `PyCon UK 2012: Create *beautiful* +command-line interfaces with Python `_ + + New in version 0.6.1: + + - Fix issue `#85 `_ + which caused improper handling of ``[options]`` shortcut + if it was present several times. + + New in version 0.6.0: + + - New argument ``options_first``, disallows interspersing options + and arguments. If you supply ``options_first=True`` to + ``docopt``, it will interpret all arguments as positional + arguments after first positional argument. + + - If option with argument could be repeated, its default value + will be interpreted as space-separated list. E.g. with + ``[default: ./here ./there]`` will be interpreted as + ``['./here', './there']``. + + Breaking changes: + + - Meaning of ``[options]`` shortcut slightly changed. Previously + it ment *"any known option"*. Now it means *"any option not in + usage-pattern"*. This avoids the situation when an option is + allowed to be repeated unintentionaly. + + - ``argv`` is ``None`` by default, not ``sys.argv[1:]``. + This allows ``docopt`` to always use the *latest* ``sys.argv``, + not ``sys.argv`` during import time. + +Isn't it awesome how ``optparse`` and ``argparse`` generate help +messages based on your code?! + +*Hell no!* You know what's awesome? It's when the option parser *is* +generated based on the beautiful help message that you write yourself! +This way you don't need to write this stupid repeatable parser-code, +and instead can write only the help message--*the way you want it*. + +**docopt** helps you create most beautiful command-line interfaces +*easily*: + +.. code:: python + + """Naval Fate. + + Usage: + naval_fate.py ship new ... + naval_fate.py ship move [--speed=] + naval_fate.py ship shoot + naval_fate.py mine (set|remove) [--moored | --drifting] + naval_fate.py (-h | --help) + naval_fate.py --version + + Options: + -h --help Show this screen. + --version Show version. + --speed= Speed in knots [default: 10]. + --moored Moored (anchored) mine. + --drifting Drifting mine. + + """ + from docopt import docopt + + + if __name__ == '__main__': + arguments = docopt(__doc__, version='Naval Fate 2.0') + print(arguments) + +Beat that! The option parser is generated based on the docstring above +that is passed to ``docopt`` function. ``docopt`` parses the usage +pattern (``"Usage: ..."``) and option descriptions (lines starting +with dash "``-``") and ensures that the program invocation matches the +usage pattern; it parses options, arguments and commands based on +that. The basic idea is that *a good help message has all necessary +information in it to make a parser*. + +Also, `PEP 257 `_ recommends +putting help message in the module docstrings. + +Installation +====================================================================== + +Use `pip `_ or easy_install:: + + pip install docopt==0.6.2 + +Alternatively, you can just drop ``docopt.py`` file into your +project--it is self-contained. + +**docopt** is tested with Python 2.5, 2.6, 2.7, 3.2, 3.3 and PyPy. + +API +====================================================================== + +.. code:: python + + from docopt import docopt + +.. code:: python + + docopt(doc, argv=None, help=True, version=None, options_first=False) + +``docopt`` takes 1 required and 4 optional arguments: + +- ``doc`` could be a module docstring (``__doc__``) or some other + string that contains a **help message** that will be parsed to + create the option parser. The simple rules of how to write such a + help message are given in next sections. Here is a quick example of + such a string: + +.. code:: python + + """Usage: my_program.py [-hso FILE] [--quiet | --verbose] [INPUT ...] + + -h --help show this + -s --sorted sorted output + -o FILE specify output file [default: ./test.txt] + --quiet print less text + --verbose print more text + + """ + +- ``argv`` is an optional argument vector; by default ``docopt`` uses + the argument vector passed to your program (``sys.argv[1:]``). + Alternatively you can supply a list of strings like ``['--verbose', + '-o', 'hai.txt']``. + +- ``help``, by default ``True``, specifies whether the parser should + automatically print the help message (supplied as ``doc``) and + terminate, in case ``-h`` or ``--help`` option is encountered + (options should exist in usage pattern, more on that below). If you + want to handle ``-h`` or ``--help`` options manually (as other + options), set ``help=False``. + +- ``version``, by default ``None``, is an optional argument that + specifies the version of your program. If supplied, then, (assuming + ``--version`` option is mentioned in usage pattern) when parser + encounters the ``--version`` option, it will print the supplied + version and terminate. ``version`` could be any printable object, + but most likely a string, e.g. ``"2.1.0rc1"``. + + Note, when ``docopt`` is set to automatically handle ``-h``, + ``--help`` and ``--version`` options, you still need to mention + them in usage pattern for this to work. Also, for your users to + know about them. + +- ``options_first``, by default ``False``. If set to ``True`` will + disallow mixing options and positional argument. I.e. after first + positional argument, all arguments will be interpreted as positional + even if the look like options. This can be used for strict + compatibility with POSIX, or if you want to dispatch your arguments + to other programs. + +The **return** value is a simple dictionary with options, arguments +and commands as keys, spelled exactly like in your help message. Long +versions of options are given priority. For example, if you invoke the +top example as:: + + naval_fate.py ship Guardian move 100 150 --speed=15 + +the return dictionary will be: + +.. code:: python + + {'--drifting': False, 'mine': False, + '--help': False, 'move': True, + '--moored': False, 'new': False, + '--speed': '15', 'remove': False, + '--version': False, 'set': False, + '': ['Guardian'], 'ship': True, + '': '100', 'shoot': False, + '': '150'} + +Help message format +====================================================================== + +Help message consists of 2 parts: + +- Usage pattern, e.g.:: + + Usage: my_program.py [-hso FILE] [--quiet | --verbose] [INPUT ...] + +- Option descriptions, e.g.:: + + -h --help show this + -s --sorted sorted output + -o FILE specify output file [default: ./test.txt] + --quiet print less text + --verbose print more text + +Their format is described below; other text is ignored. + +Usage pattern format +---------------------------------------------------------------------- + +**Usage pattern** is a substring of ``doc`` that starts with +``usage:`` (case *insensitive*) and ends with a *visibly* empty line. +Minimum example: + +.. code:: python + + """Usage: my_program.py + + """ + +The first word after ``usage:`` is interpreted as your program's name. +You can specify your program's name several times to signify several +exclusive patterns: + +.. code:: python + + """Usage: my_program.py FILE + my_program.py COUNT FILE + + """ + +Each pattern can consist of the following elements: + +- ****, **ARGUMENTS**. Arguments are specified as either + upper-case words, e.g. ``my_program.py CONTENT-PATH`` or words + surrounded by angular brackets: ``my_program.py ``. +- **--options**. Options are words started with dash (``-``), e.g. + ``--output``, ``-o``. You can "stack" several of one-letter + options, e.g. ``-oiv`` which will be the same as ``-o -i -v``. The + options can have arguments, e.g. ``--input=FILE`` or ``-i FILE`` or + even ``-iFILE``. However it is important that you specify option + descriptions if you want for option to have an argument, a default + value, or specify synonymous short/long versions of option (see next + section on option descriptions). +- **commands** are words that do *not* follow the described above + conventions of ``--options`` or ```` or ``ARGUMENTS``, + plus two special commands: dash "``-``" and double dash "``--``" + (see below). + +Use the following constructs to specify patterns: + +- **[ ]** (brackets) **optional** elements. e.g.: ``my_program.py + [-hvqo FILE]`` +- **( )** (parens) **required** elements. All elements that are *not* + put in **[ ]** are also required, e.g.: ``my_program.py + --path= ...`` is the same as ``my_program.py + (--path= ...)``. (Note, "required options" might be not + a good idea for your users). +- **|** (pipe) **mutualy exclusive** elements. Group them using **( + )** if one of the mutually exclusive elements is required: + ``my_program.py (--clockwise | --counter-clockwise) TIME``. Group + them using **[ ]** if none of the mutually-exclusive elements are + required: ``my_program.py [--left | --right]``. +- **...** (ellipsis) **one or more** elements. To specify that + arbitrary number of repeating elements could be accepted, use + ellipsis (``...``), e.g. ``my_program.py FILE ...`` means one or + more ``FILE``-s are accepted. If you want to accept zero or more + elements, use brackets, e.g.: ``my_program.py [FILE ...]``. Ellipsis + works as a unary operator on the expression to the left. +- **[options]** (case sensitive) shortcut for any options. You can + use it if you want to specify that the usage pattern could be + provided with any options defined below in the option-descriptions + and do not want to enumerate them all in usage-pattern. - + "``[--]``". Double dash "``--``" is used by convention to separate + positional arguments that can be mistaken for options. In order to + support this convention add "``[--]``" to you usage patterns. - + "``[-]``". Single dash "``-``" is used by convention to signify that + ``stdin`` is used instead of a file. To support this add "``[-]``" + to you usage patterns. "``-``" act as a normal command. + +If your pattern allows to match argument-less option (a flag) several +times:: + + Usage: my_program.py [-v | -vv | -vvv] + +then number of occurences of the option will be counted. I.e. +``args['-v']`` will be ``2`` if program was invoked as ``my_program +-vv``. Same works for commands. + +If your usage patterns allows to match same-named option with argument +or positional argument several times, the matched arguments will be +collected into a list:: + + Usage: my_program.py --path=... + +I.e. invoked with ``my_program.py file1 file2 --path=./here +--path=./there`` the returned dict will contain ``args[''] == +['file1', 'file2']`` and ``args['--path'] == ['./here', './there']``. + + +Option descriptions format +---------------------------------------------------------------------- + +**Option descriptions** consist of a list of options that you put +below your usage patterns. + +It is necessary to list option descriptions in order to specify: + +- synonymous short and long options, +- if an option has an argument, +- if option's argument has a default value. + +The rules are as follows: + +- Every line in ``doc`` that starts with ``-`` or ``--`` (not counting + spaces) is treated as an option description, e.g.:: + + Options: + --verbose # GOOD + -o FILE # GOOD + Other: --bad # BAD, line does not start with dash "-" + +- To specify that option has an argument, put a word describing that + argument after space (or equals "``=``" sign) as shown below. Follow + either or UPPER-CASE convention for options' + arguments. You can use comma if you want to separate options. In + the example below, both lines are valid, however you are recommended + to stick to a single style.:: + + -o FILE --output=FILE # without comma, with "=" sign + -i , --input # with comma, wihtout "=" sing + +- Use two spaces to separate options with their informal description:: + + --verbose More text. # BAD, will be treated as if verbose option had + # an argument "More", so use 2 spaces instead + -q Quit. # GOOD + -o FILE Output file. # GOOD + --stdout Use stdout. # GOOD, 2 spaces + +- If you want to set a default value for an option with an argument, + put it into the option-description, in form ``[default: + ]``:: + + --coefficient=K The K coefficient [default: 2.95] + --output=FILE Output file [default: test.txt] + --directory=DIR Some directory [default: ./] + +- If the option is not repeatable, the value inside ``[default: ...]`` + will be interpeted as string. If it *is* repeatable, it will be + splited into a list on whitespace:: + + Usage: my_program.py [--repeatable= --repeatable=] + [--another-repeatable=]... + [--not-repeatable=] + + # will be ['./here', './there'] + --repeatable= [default: ./here ./there] + + # will be ['./here'] + --another-repeatable= [default: ./here] + + # will be './here ./there', because it is not repeatable + --not-repeatable= [default: ./here ./there] + +Examples +---------------------------------------------------------------------- + +We have an extensive list of `examples +`_ which cover +every aspect of functionality of **docopt**. Try them out, read the +source if in doubt. + +Subparsers, multi-level help and *huge* applications (like git) +---------------------------------------------------------------------- + +If you want to split your usage-pattern into several, implement +multi-level help (whith separate help-screen for each subcommand), +want to interface with existing scripts that don't use **docopt**, or +you're building the next "git", you will need the new ``options_first`` +parameter (described in API section above). To get you started quickly +we implemented a subset of git command-line interface as an example: +`examples/git +`_ + + +Data validation +---------------------------------------------------------------------- + +**docopt** does one thing and does it well: it implements your +command-line interface. However it does not validate the input data. +On the other hand there are libraries like `python schema +`_ which make validating data a +breeze. Take a look at `validation_example.py +`_ +which uses **schema** to validate data and report an error to the +user. + +Development +====================================================================== + +We would *love* to hear what you think about **docopt** on our `issues +page `_ + +Make pull requrests, report bugs, suggest ideas and discuss +**docopt**. You can also drop a line directly to +. + +Porting ``docopt`` to other languages +====================================================================== + +We think **docopt** is so good, we want to share it beyond the Python +community! + +The follosing ports are available: + +- `Ruby port `_ +- `CoffeeScript port `_ +- `Lua port `_ +- `PHP port `_ + +But you can always create a port for your favorite language! You are +encouraged to use the Python version as a reference implementation. A +Language-agnostic test suite is bundled with `Python implementation +`_. + +Porting discussion is on `issues page +`_. + +Changelog +====================================================================== + +**docopt** follows `semantic versioning `_. The +first release with stable API will be 1.0.0 (soon). Until then, you +are encouraged to specify explicitly the version in your dependency +tools, e.g.:: + + pip install docopt==0.6.2 + +- 0.6.2 `Wheel `_ support. +- 0.6.1 Bugfix release. +- 0.6.0 ``options_first`` parameter. + **Breaking changes**: Corrected ``[options]`` meaning. + ``argv`` defaults to ``None``. +- 0.5.0 Repeated options/commands are counted or accumulated into a + list. +- 0.4.2 Bugfix release. +- 0.4.0 Option descriptions become optional, + support for "``--``" and "``-``" commands. +- 0.3.0 Support for (sub)commands like `git remote add`. + Introduce ``[options]`` shortcut for any options. + **Breaking changes**: ``docopt`` returns dictionary. +- 0.2.0 Usage pattern matching. Positional arguments parsing based on + usage patterns. + **Breaking changes**: ``docopt`` returns namespace (for arguments), + not list. Usage pattern is formalized. +- 0.1.0 Initial release. Options-parsing only (based on options + description). + + diff --git a/venv/Lib/site-packages/docopt-0.6.2.dist-info/RECORD b/venv/Lib/site-packages/docopt-0.6.2.dist-info/RECORD new file mode 100644 index 0000000..b35449c --- /dev/null +++ b/venv/Lib/site-packages/docopt-0.6.2.dist-info/RECORD @@ -0,0 +1,9 @@ +__pycache__/docopt.cpython-39.pyc,, +docopt-0.6.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +docopt-0.6.2.dist-info/LICENSE-MIT,sha256=PV33j1kv8kM8PGzkmECRt_SXBZ3bjGsIoGG6SON7Z_I,1097 +docopt-0.6.2.dist-info/METADATA,sha256=QZKAtY4wGrhkhcVlKZk_NJATuwpTdpcV-ciDGJbKCNo,17956 +docopt-0.6.2.dist-info/RECORD,, +docopt-0.6.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +docopt-0.6.2.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 +docopt-0.6.2.dist-info/top_level.txt,sha256=xAvL2ywTOdLde8wxTVye1299j65YdK3cM5963wNy5SU,7 +docopt.py,sha256=RMZQ69gz2FLIcx-j8MV1lQYwliIwDkwZVKVA14VyzFQ,19946 diff --git a/venv/Lib/site-packages/docopt-0.6.2.dist-info/REQUESTED b/venv/Lib/site-packages/docopt-0.6.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/docopt-0.6.2.dist-info/WHEEL b/venv/Lib/site-packages/docopt-0.6.2.dist-info/WHEEL new file mode 100644 index 0000000..01b8fc7 --- /dev/null +++ b/venv/Lib/site-packages/docopt-0.6.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/Lib/site-packages/docopt-0.6.2.dist-info/top_level.txt b/venv/Lib/site-packages/docopt-0.6.2.dist-info/top_level.txt new file mode 100644 index 0000000..e5ed2a0 --- /dev/null +++ b/venv/Lib/site-packages/docopt-0.6.2.dist-info/top_level.txt @@ -0,0 +1 @@ +docopt diff --git a/venv/Lib/site-packages/docopt.py b/venv/Lib/site-packages/docopt.py new file mode 100644 index 0000000..7b927e2 --- /dev/null +++ b/venv/Lib/site-packages/docopt.py @@ -0,0 +1,579 @@ +"""Pythonic command-line interface parser that will make you smile. + + * http://docopt.org + * Repository and issue-tracker: https://github.com/docopt/docopt + * Licensed under terms of MIT license (see LICENSE-MIT) + * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com + +""" +import sys +import re + + +__all__ = ['docopt'] +__version__ = '0.6.2' + + +class DocoptLanguageError(Exception): + + """Error in construction of usage-message by developer.""" + + +class DocoptExit(SystemExit): + + """Exit in case user invoked program with incorrect arguments.""" + + usage = '' + + def __init__(self, message=''): + SystemExit.__init__(self, (message + '\n' + self.usage).strip()) + + +class Pattern(object): + + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self)) + + def fix(self): + self.fix_identities() + self.fix_repeating_arguments() + return self + + def fix_identities(self, uniq=None): + """Make pattern-tree tips point to same object if they are equal.""" + if not hasattr(self, 'children'): + return self + uniq = list(set(self.flat())) if uniq is None else uniq + for i, c in enumerate(self.children): + if not hasattr(c, 'children'): + assert c in uniq + self.children[i] = uniq[uniq.index(c)] + else: + c.fix_identities(uniq) + + def fix_repeating_arguments(self): + """Fix elements that should accumulate/increment values.""" + either = [list(c.children) for c in self.either.children] + for case in either: + for e in [c for c in case if case.count(c) > 1]: + if type(e) is Argument or type(e) is Option and e.argcount: + if e.value is None: + e.value = [] + elif type(e.value) is not list: + e.value = e.value.split() + if type(e) is Command or type(e) is Option and e.argcount == 0: + e.value = 0 + return self + + @property + def either(self): + """Transform pattern into an equivalent, with only top-level Either.""" + # Currently the pattern will not be equivalent, but more "narrow", + # although good enough to reason about list arguments. + ret = [] + groups = [[self]] + while groups: + children = groups.pop(0) + types = [type(c) for c in children] + if Either in types: + either = [c for c in children if type(c) is Either][0] + children.pop(children.index(either)) + for c in either.children: + groups.append([c] + children) + elif Required in types: + required = [c for c in children if type(c) is Required][0] + children.pop(children.index(required)) + groups.append(list(required.children) + children) + elif Optional in types: + optional = [c for c in children if type(c) is Optional][0] + children.pop(children.index(optional)) + groups.append(list(optional.children) + children) + elif AnyOptions in types: + optional = [c for c in children if type(c) is AnyOptions][0] + children.pop(children.index(optional)) + groups.append(list(optional.children) + children) + elif OneOrMore in types: + oneormore = [c for c in children if type(c) is OneOrMore][0] + children.pop(children.index(oneormore)) + groups.append(list(oneormore.children) * 2 + children) + else: + ret.append(children) + return Either(*[Required(*e) for e in ret]) + + +class ChildPattern(Pattern): + + def __init__(self, name, value=None): + self.name = name + self.value = value + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) + + def flat(self, *types): + return [self] if not types or type(self) in types else [] + + def match(self, left, collected=None): + collected = [] if collected is None else collected + pos, match = self.single_match(left) + if match is None: + return False, left, collected + left_ = left[:pos] + left[pos + 1:] + same_name = [a for a in collected if a.name == self.name] + if type(self.value) in (int, list): + if type(self.value) is int: + increment = 1 + else: + increment = ([match.value] if type(match.value) is str + else match.value) + if not same_name: + match.value = increment + return True, left_, collected + [match] + same_name[0].value += increment + return True, left_, collected + return True, left_, collected + [match] + + +class ParentPattern(Pattern): + + def __init__(self, *children): + self.children = list(children) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(repr(a) for a in self.children)) + + def flat(self, *types): + if type(self) in types: + return [self] + return sum([c.flat(*types) for c in self.children], []) + + +class Argument(ChildPattern): + + def single_match(self, left): + for n, p in enumerate(left): + if type(p) is Argument: + return n, Argument(self.name, p.value) + return None, None + + @classmethod + def parse(class_, source): + name = re.findall('(<\S*?>)', source)[0] + value = re.findall('\[default: (.*)\]', source, flags=re.I) + return class_(name, value[0] if value else None) + + +class Command(Argument): + + def __init__(self, name, value=False): + self.name = name + self.value = value + + def single_match(self, left): + for n, p in enumerate(left): + if type(p) is Argument: + if p.value == self.name: + return n, Command(self.name, True) + else: + break + return None, None + + +class Option(ChildPattern): + + def __init__(self, short=None, long=None, argcount=0, value=False): + assert argcount in (0, 1) + self.short, self.long = short, long + self.argcount, self.value = argcount, value + self.value = None if value is False and argcount else value + + @classmethod + def parse(class_, option_description): + short, long, argcount, value = None, None, 0, False + options, _, description = option_description.strip().partition(' ') + options = options.replace(',', ' ').replace('=', ' ') + for s in options.split(): + if s.startswith('--'): + long = s + elif s.startswith('-'): + short = s + else: + argcount = 1 + if argcount: + matched = re.findall('\[default: (.*)\]', description, flags=re.I) + value = matched[0] if matched else None + return class_(short, long, argcount, value) + + def single_match(self, left): + for n, p in enumerate(left): + if self.name == p.name: + return n, p + return None, None + + @property + def name(self): + return self.long or self.short + + def __repr__(self): + return 'Option(%r, %r, %r, %r)' % (self.short, self.long, + self.argcount, self.value) + + +class Required(ParentPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + l = left + c = collected + for p in self.children: + matched, l, c = p.match(l, c) + if not matched: + return False, left, collected + return True, l, c + + +class Optional(ParentPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + for p in self.children: + m, left, collected = p.match(left, collected) + return True, left, collected + + +class AnyOptions(Optional): + + """Marker/placeholder for [options] shortcut.""" + + +class OneOrMore(ParentPattern): + + def match(self, left, collected=None): + assert len(self.children) == 1 + collected = [] if collected is None else collected + l = left + c = collected + l_ = None + matched = True + times = 0 + while matched: + # could it be that something didn't match but changed l or c? + matched, l, c = self.children[0].match(l, c) + times += 1 if matched else 0 + if l_ == l: + break + l_ = l + if times >= 1: + return True, l, c + return False, left, collected + + +class Either(ParentPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + outcomes = [] + for p in self.children: + matched, _, _ = outcome = p.match(left, collected) + if matched: + outcomes.append(outcome) + if outcomes: + return min(outcomes, key=lambda outcome: len(outcome[1])) + return False, left, collected + + +class TokenStream(list): + + def __init__(self, source, error): + self += source.split() if hasattr(source, 'split') else source + self.error = error + + def move(self): + return self.pop(0) if len(self) else None + + def current(self): + return self[0] if len(self) else None + + +def parse_long(tokens, options): + """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" + long, eq, value = tokens.move().partition('=') + assert long.startswith('--') + value = None if eq == value == '' else value + similar = [o for o in options if o.long == long] + if tokens.error is DocoptExit and similar == []: # if no exact match + similar = [o for o in options if o.long and o.long.startswith(long)] + if len(similar) > 1: # might be simply specified ambiguously 2+ times? + raise tokens.error('%s is not a unique prefix: %s?' % + (long, ', '.join(o.long for o in similar))) + elif len(similar) < 1: + argcount = 1 if eq == '=' else 0 + o = Option(None, long, argcount) + options.append(o) + if tokens.error is DocoptExit: + o = Option(None, long, argcount, value if argcount else True) + else: + o = Option(similar[0].short, similar[0].long, + similar[0].argcount, similar[0].value) + if o.argcount == 0: + if value is not None: + raise tokens.error('%s must not have an argument' % o.long) + else: + if value is None: + if tokens.current() is None: + raise tokens.error('%s requires argument' % o.long) + value = tokens.move() + if tokens.error is DocoptExit: + o.value = value if value is not None else True + return [o] + + +def parse_shorts(tokens, options): + """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" + token = tokens.move() + assert token.startswith('-') and not token.startswith('--') + left = token.lstrip('-') + parsed = [] + while left != '': + short, left = '-' + left[0], left[1:] + similar = [o for o in options if o.short == short] + if len(similar) > 1: + raise tokens.error('%s is specified ambiguously %d times' % + (short, len(similar))) + elif len(similar) < 1: + o = Option(short, None, 0) + options.append(o) + if tokens.error is DocoptExit: + o = Option(short, None, 0, True) + else: # why copying is necessary here? + o = Option(short, similar[0].long, + similar[0].argcount, similar[0].value) + value = None + if o.argcount != 0: + if left == '': + if tokens.current() is None: + raise tokens.error('%s requires argument' % short) + value = tokens.move() + else: + value = left + left = '' + if tokens.error is DocoptExit: + o.value = value if value is not None else True + parsed.append(o) + return parsed + + +def parse_pattern(source, options): + tokens = TokenStream(re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source), + DocoptLanguageError) + result = parse_expr(tokens, options) + if tokens.current() is not None: + raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) + return Required(*result) + + +def parse_expr(tokens, options): + """expr ::= seq ( '|' seq )* ;""" + seq = parse_seq(tokens, options) + if tokens.current() != '|': + return seq + result = [Required(*seq)] if len(seq) > 1 else seq + while tokens.current() == '|': + tokens.move() + seq = parse_seq(tokens, options) + result += [Required(*seq)] if len(seq) > 1 else seq + return [Either(*result)] if len(result) > 1 else result + + +def parse_seq(tokens, options): + """seq ::= ( atom [ '...' ] )* ;""" + result = [] + while tokens.current() not in [None, ']', ')', '|']: + atom = parse_atom(tokens, options) + if tokens.current() == '...': + atom = [OneOrMore(*atom)] + tokens.move() + result += atom + return result + + +def parse_atom(tokens, options): + """atom ::= '(' expr ')' | '[' expr ']' | 'options' + | long | shorts | argument | command ; + """ + token = tokens.current() + result = [] + if token in '([': + tokens.move() + matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] + result = pattern(*parse_expr(tokens, options)) + if tokens.move() != matching: + raise tokens.error("unmatched '%s'" % token) + return [result] + elif token == 'options': + tokens.move() + return [AnyOptions()] + elif token.startswith('--') and token != '--': + return parse_long(tokens, options) + elif token.startswith('-') and token not in ('-', '--'): + return parse_shorts(tokens, options) + elif token.startswith('<') and token.endswith('>') or token.isupper(): + return [Argument(tokens.move())] + else: + return [Command(tokens.move())] + + +def parse_argv(tokens, options, options_first=False): + """Parse command-line argument vector. + + If options_first: + argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; + else: + argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; + + """ + parsed = [] + while tokens.current() is not None: + if tokens.current() == '--': + return parsed + [Argument(None, v) for v in tokens] + elif tokens.current().startswith('--'): + parsed += parse_long(tokens, options) + elif tokens.current().startswith('-') and tokens.current() != '-': + parsed += parse_shorts(tokens, options) + elif options_first: + return parsed + [Argument(None, v) for v in tokens] + else: + parsed.append(Argument(None, tokens.move())) + return parsed + + +def parse_defaults(doc): + # in python < 2.7 you can't pass flags=re.MULTILINE + split = re.split('\n *(<\S+?>|-\S+?)', doc)[1:] + split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] + options = [Option.parse(s) for s in split if s.startswith('-')] + #arguments = [Argument.parse(s) for s in split if s.startswith('<')] + #return options, arguments + return options + + +def printable_usage(doc): + # in python < 2.7 you can't pass flags=re.IGNORECASE + usage_split = re.split(r'([Uu][Ss][Aa][Gg][Ee]:)', doc) + if len(usage_split) < 3: + raise DocoptLanguageError('"usage:" (case-insensitive) not found.') + if len(usage_split) > 3: + raise DocoptLanguageError('More than one "usage:" (case-insensitive).') + return re.split(r'\n\s*\n', ''.join(usage_split[1:]))[0].strip() + + +def formal_usage(printable_usage): + pu = printable_usage.split()[1:] # split and drop "usage:" + return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' + + +def extras(help, version, options, doc): + if help and any((o.name in ('-h', '--help')) and o.value for o in options): + print(doc.strip("\n")) + sys.exit() + if version and any(o.name == '--version' and o.value for o in options): + print(version) + sys.exit() + + +class Dict(dict): + def __repr__(self): + return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) + + +def docopt(doc, argv=None, help=True, version=None, options_first=False): + """Parse `argv` based on command-line interface described in `doc`. + + `docopt` creates your command-line interface based on its + description that you pass as `doc`. Such description can contain + --options, , commands, which could be + [optional], (required), (mutually | exclusive) or repeated... + + Parameters + ---------- + doc : str + Description of your command-line interface. + argv : list of str, optional + Argument vector to be parsed. sys.argv[1:] is used if not + provided. + help : bool (default: True) + Set to False to disable automatic help on -h or --help + options. + version : any object + If passed, the object will be printed if --version is in + `argv`. + options_first : bool (default: False) + Set to True to require options preceed positional arguments, + i.e. to forbid options and positional arguments intermix. + + Returns + ------- + args : dict + A dictionary, where keys are names of command-line elements + such as e.g. "--verbose" and "", and values are the + parsed values of those elements. + + Example + ------- + >>> from docopt import docopt + >>> doc = ''' + Usage: + my_program tcp [--timeout=] + my_program serial [--baud=] [--timeout=] + my_program (-h | --help | --version) + + Options: + -h, --help Show this screen and exit. + --baud= Baudrate [default: 9600] + ''' + >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] + >>> docopt(doc, argv) + {'--baud': '9600', + '--help': False, + '--timeout': '30', + '--version': False, + '': '127.0.0.1', + '': '80', + 'serial': False, + 'tcp': True} + + See also + -------- + * For video introduction see http://docopt.org + * Full documentation is available in README.rst as well as online + at https://github.com/docopt/docopt#readme + + """ + if argv is None: + argv = sys.argv[1:] + DocoptExit.usage = printable_usage(doc) + options = parse_defaults(doc) + pattern = parse_pattern(formal_usage(DocoptExit.usage), options) + # [default] syntax for argument is disabled + #for a in pattern.flat(Argument): + # same_name = [d for d in arguments if d.name == a.name] + # if same_name: + # a.value = same_name[0].value + argv = parse_argv(TokenStream(argv, DocoptExit), list(options), + options_first) + pattern_options = set(pattern.flat(Option)) + for ao in pattern.flat(AnyOptions): + doc_options = parse_defaults(doc) + ao.children = list(set(doc_options) - pattern_options) + #if any_options: + # ao.children += [Option(o.short, o.long, o.argcount) + # for o in argv if type(o) is Option] + extras(help, version, argv, doc) + matched, left, collected = pattern.fix().match(argv) + if matched and left == []: # better error message if left? + return Dict((a.name, a.value) for a in (pattern.flat() + collected)) + raise DocoptExit() diff --git a/venv/Lib/site-packages/dotenv/__init__.py b/venv/Lib/site-packages/dotenv/__init__.py new file mode 100644 index 0000000..3512d10 --- /dev/null +++ b/venv/Lib/site-packages/dotenv/__init__.py @@ -0,0 +1,49 @@ +from typing import Any, Optional + +from .main import (dotenv_values, find_dotenv, get_key, load_dotenv, set_key, + unset_key) + + +def load_ipython_extension(ipython: Any) -> None: + from .ipython import load_ipython_extension + load_ipython_extension(ipython) + + +def get_cli_string( + path: Optional[str] = None, + action: Optional[str] = None, + key: Optional[str] = None, + value: Optional[str] = None, + quote: Optional[str] = None, +): + """Returns a string suitable for running as a shell script. + + Useful for converting a arguments passed to a fabric task + to be passed to a `local` or `run` command. + """ + command = ['dotenv'] + if quote: + command.append('-q %s' % quote) + if path: + command.append('-f %s' % path) + if action: + command.append(action) + if key: + command.append(key) + if value: + if ' ' in value: + command.append('"%s"' % value) + else: + command.append(value) + + return ' '.join(command).strip() + + +__all__ = ['get_cli_string', + 'load_dotenv', + 'dotenv_values', + 'get_key', + 'set_key', + 'unset_key', + 'find_dotenv', + 'load_ipython_extension'] diff --git a/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74b37f08b6a91370f4f9db2e5a2172cad3c94210 GIT binary patch literal 1231 zcmZ`(JCD>b5VrH$_i;Q00-*?rVv#s{gb)ZybVwkYXi?4^dvoC&C)mltoCO96!FqZ*1#N!Ts*ESofP>}h{YNrUOXw53sW@WcFnRQ*f)!9*mu&jP(_ob zd-cY7!8#he6roez=?o`|?r}V@-#RNykU!J)FnV7|<(S^!m(-sX|N)fLr9qU#p6f&o9D5Q){ri;4S8;@fk z$nmO`PBp94TIj}U$4uwje8)92M)0_)z{83yb&3y70QMfQaz9KSxB=! zhj1R@0>VWAJLv7ZuGzva%6G8aGg+kDq)b6(t^tr~L<73hk1=o@L%QpYJ$)7S=R?Qz z5at@ouCZhE2m${P8q44J%|6n6$41SjhO}vX6xs){LCFfWJ?`|K4QKL?A?$hU!~L8? uJ4jO|Wt#pK3BCmF!WrHxE8a?RAK#(54zTOR!EQuo|NZc2^y~XEz5WxsM=V_c literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1980be39c75950d8fc5c940b110fdebae409cd4b GIT binary patch literal 4821 zcma)9TXWmS6~^L5in>_7$(EDYuwunxY|_rHbrL5{VmULOdSusjnv{{tK-{GS2?Xd} zP!|sM&?+XQfhWTd=hG#prJ5Ix4^v;Rhj@R%ye#7sS8YO1+ z$8?W!qx`j%%(<2r+2YKipJ1z%y;1J&Gpg^6agS<$vJ(5I#XauF#(iH>${G& z^aj6)|F`%%`uU=`DoSE*8l2$o!s7S%`v>FgutwE<6TX-eb8UOt5^wVlVC8T4Z@+gN z*Ti*xYt8<_mJM-f2Yle{u~q%>Ct}`0mF3>Uv@4RTlRKX$Te&+QwX)p3AE~Urb}tK* z5FHhCx3ZNqsqr)u$$Ai}AW5?zN>mocvEac*l&u7FG$;%R0Pxp7R2Iuz(qUk z#hD_ThOc3L(CSGkk}TLrgRGU{e^z+Ec9u6nuC$~JSCz{|a2sb-Sgmm=%Qq~#L9ii_$F z?mM{E9E!*qSWRnS53E)2@wPQ^bj?PM>6&xf;x2gV^*`4uG)B{|8b03u*<9>d)wJ|Gk34d zT_EnZ?J0|5!LwW31J-{3@_eMaakvF`#hMxlR0rJfz#E!~b$J06{5<_IgvZa!s+mLT ziA`um{YR#k@v}FpKABYu)e-p`I^=e$a<_|4xgROR8X(bH%g4S5`9nb25t)#(L~T9b z*Fkg9T1l(k;qK%Ug*Jq$$w{TyTF%z>KAGYT>g~obV-_`q5Nla(g^e+n`OJRdyJa@X zNe%}%H>0dtGH@QQ?>Oxtv%OYJDD^U`4!rGs45KDcSU$0~!lnT1 z8C$-KHxM}OMS5V38B&mv193m`` z0);hFhPbnjL?$DMs|HK&WzdE&kdzLgGvt+*V1Nv$FnAe?${Bj|CKXqx(8#?-HF8yr zp}dLOF^Uj+jWOdSCS?5rI4IOSS}AI3c}Lk;0eoY&d=KwiN6{jOAEHHl57E*!1o&+S zyaF-RiF@)`sW^--Ea5ecq@>)IS*#l zabi&tdq^w;O5vt@`wuDg4z(m5J>s99*6KgvrJ{V7AgRDDT{dY;VpwA;T28?FW-nPw z(v75Patei5KG{l>Ol)RNkTFAeQ~mR`8dfA_#KnzTElGz@{og+*-VY52cP| znvjs2z^4-!q&P|(HX5b@bx1RrDvA5~Xe;e>!i2|BB7XWWuBwf&9?E4U$MLKm=? zKDkIX!b{{JS%t(0F63n+kRTG5GQ?%1e}i(gVlgIzI||D|{(;<23I(8y^YpLXOW<7* zH^El>*wtG5>MLV`cCur!j8;plCaq8;#FPg-y#fmBIK{0VlkVRwH8WtDwaV7*y5^h?2bJ3tDzMD%ry0H|!rixhHua)^ zPWF=e?HEa6tUj$Dw(Flo-k(&dap;dUaogv=Qgtu_h@34UH^cW%M$UxAMS{Bqv7j{-X4e5*3~RKF%`pU?!xyO9TD&g?4ae~_G*(J=8x0iBVK^@5ENoh-um zn6?a^s6ay}#>cGEqeRK^(M$pjQ;_KwN0q`XGtLu9`Qev1EuHEm$P1(j<7Of@NRn`5 zTI#h`)JI;fa2|=}=R}Q?32z^wp1Z9SnW*3E@(?)}@xzE3#=`kX+?6twxvw&g0FZn7IAdJSr5SWCX=1m| zgml!Rlu(YM$h~Em_PV)mqKl6Exx1Q1N$w+Jw>Eh0QjXj3@2sRz3un789z3|;m?#cF zMNn#tbwj1#-8wAgr8}mg^LYatX=%7yxI#tpE--No+uNEFYdT{6Noius|!m2cxn*o(!dL<$w7C_@v*keS&i@v|rG^K8uV*?B01 y{laxfN6bHVNI$#mO4&yYo*Ji;dR-hdwF@VT|IrD2DR%Tft~beM&R(3Uu>S$Nf4q4B literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9aaca744d668ce8231fd044c09cdf3047f97b2ef GIT binary patch literal 1477 zcmZWpTaOzx6!v9qnIui9w&j6E8X>Jv9+L752r6|ev=S^;m+L5K>WrOa%3Ol&gzhN& zlzrX*0FV47zw*?-&E+@fjcc%(;C2Y}W6G2-**&n7!>H^t)A#8_c1{(A9S! zIN~@*1&(n+Vp7nUVmn82r*LDp@M5pJlB1@GC^Q;s}VHMMBJ}OCbrWDu&OfJ@iD76_hE6G7u zX0T#X2|J;*%*w@UY~2#moo6Lac%?R>9(4Rn8-+<6)i(cX_-jN%!B`?Vht%sU!DeM98=Z1cZ%2Nkv=lTqzj2>mH zhE0>zwR{MUyU^7T1nT#QhY23y9u9Co>yv-+ZqojKISNdWBqb|El9(__ii)puJMJaP zPivO9E7Ajp(%uAy!nu;vsy19x%#nE4&em}l8`)|L@9*TCd*MB;(mueW!HYGRCV z?C*j4PqOA=a~JFv6Y&Zz2n7e}3}UN>phUh0&zJK}`=0t*U literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c38b3dfd018533325421d5127c657d990b67da03 GIT binary patch literal 9841 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 IjQRM#0h_c|9{>OV literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f30a0a299efe5f8c06085f78ada80aee377ade54 GIT binary patch literal 5876 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fce3e33259be730dc41aba7b1b1328117bb06eee GIT binary patch literal 3513 zcma)9TW{RP6`tX6NosFe*RC72P3xp-W2Ujw8&E)S?Zy|h0TLyM5(j53@Uhc(IQ2y$#%|voH~NjS*Z0PL-{(xR=Yez; zJK8vN`prKu*^u6sOnRqIzXi>g0klB1PDQ^BeM2^(H?7`LUFE9Q7L%>79od$hFWvs8 zWAKITkaF|hM|aYAtnU8lv%zfp$y?!yWqbS9LYt43YCoPPg&n>+I=okXduD%z zpM#HUDZ&Y5(nZ8~imZj5XyX>6r zgpTo|NIi=p)7{C6_r{Y%ja6DG84Ba(D%sZn2BWod?EakT9@PJQ@?d-LX|8lW_;Y-a zs=>o-IHhU%;2_rVfqIx_x<5MDQ)+Om(&NGVqrE{sD%7n>JbVg!K9E@f!#l^Z9>sf! z%5P6TCr?rpMc*ag0|>^OJm4-rY0<`SS61v_V5TPIUf4Q?fuq^2)AWM<4bk3Zx&;NO zxq5MfkF?J8xud(V>T4uuYn8Zyp7F8gEIC#`fRfuF^OLsqq&Dn?Z4i0-2U9vs$ zOZyNsA@?8ad`+r+T$GIUf7hA)j6Z5j?DUTDs~bBd@{PN7~L#`&QRX-S(iTIAB=#1|SBML#DG zYF4czt}^PYu)Yu_a>}NSfRE+pc+syz*Vjp0%!%dDZM>f^v^n2q&ET-BU!iGRB)(7L zMD>_R!n5G&5neC3 zilY#0vy2rrV;dJ33%;sgg|Rq8Rv6nNwpQP;r^-6U29N6+yhA0QN|y2K!>(cvfph*) ze?eUec17LN0fCpr7FG2f*z_+TLQ%10@#1ohDA>P7%P9nmd;H|e+@l4N^&2p~07{zh z04*m+<}@Z)o`ESugK*In^fGcU>Z`yLE}6_xc9J1MOxTmiExNu9NulPGgNcSuL7}uwwQ8r3XHokI5E8Q7? z)-Ak}o${HFvHnx0|54BLSLe~$9eO*OO7se$UMKaJ5TS22qe+&J{t_M6<;Dd2qg2vu z0T<0loSKaTolPflG%Re1)D#3G5|xI5Jx`1`oa!7m{RU3r;h|~Z{L=;92IX=|<6*)< zad;`$W^)qjTrC#Q|A>$0RQnk&P+c5?_;Uh&4Sf&40oQk73wy@V%7}PxSl!~h;&b{R zLkk#J&Hy@)j6clAlTm{5`S{Vt_dncyq<=%B=(@JtP<6o2w^uy0xU4P_cd9$&T?%AQ X+zjB^D~`*Z!2P$|>2`Wod)NO92HT`i literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-39.pyc b/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74fd2e0893780d2eef58239edda8dcb8e938cd20 GIT binary patch literal 195 zcmYe~<>g`kg4n#&Byk}97{oyaj6jY95EpX*i4=w?h7`tN22G|aHUm8)13d#jO~zZ? z@$qG;Ma7xesXDUYFQRXW=X1UL1J None: + '''This script is used to set, get or unset values from a .env file.''' + ctx.obj = {} + ctx.obj['QUOTE'] = quote + ctx.obj['EXPORT'] = export + ctx.obj['FILE'] = file + + +@cli.command() +@click.pass_context +def list(ctx: click.Context) -> None: + '''Display all the stored key/value.''' + file = ctx.obj['FILE'] + if not os.path.isfile(file): + raise click.BadParameter( + 'Path "%s" does not exist.' % (file), + ctx=ctx + ) + dotenv_as_dict = dotenv_values(file) + for k, v in dotenv_as_dict.items(): + click.echo('%s=%s' % (k, v)) + + +@cli.command() +@click.pass_context +@click.argument('key', required=True) +@click.argument('value', required=True) +def set(ctx: click.Context, key: Any, value: Any) -> None: + '''Store the given key/value.''' + file = ctx.obj['FILE'] + quote = ctx.obj['QUOTE'] + export = ctx.obj['EXPORT'] + success, key, value = set_key(file, key, value, quote, export) + if success: + click.echo('%s=%s' % (key, value)) + else: + exit(1) + + +@cli.command() +@click.pass_context +@click.argument('key', required=True) +def get(ctx: click.Context, key: Any) -> None: + '''Retrieve the value for the given key.''' + file = ctx.obj['FILE'] + if not os.path.isfile(file): + raise click.BadParameter( + 'Path "%s" does not exist.' % (file), + ctx=ctx + ) + stored_value = get_key(file, key) + if stored_value: + click.echo(stored_value) + else: + exit(1) + + +@cli.command() +@click.pass_context +@click.argument('key', required=True) +def unset(ctx: click.Context, key: Any) -> None: + '''Removes the given key.''' + file = ctx.obj['FILE'] + quote = ctx.obj['QUOTE'] + success, key = unset_key(file, key, quote) + if success: + click.echo("Successfully removed %s" % key) + else: + exit(1) + + +@cli.command(context_settings={'ignore_unknown_options': True}) +@click.pass_context +@click.option( + "--override/--no-override", + default=True, + help="Override variables from the environment file with those from the .env file.", +) +@click.argument('commandline', nargs=-1, type=click.UNPROCESSED) +def run(ctx: click.Context, override: bool, commandline: List[str]) -> None: + """Run command with environment variables present.""" + file = ctx.obj['FILE'] + if not os.path.isfile(file): + raise click.BadParameter( + 'Invalid value for \'-f\' "%s" does not exist.' % (file), + ctx=ctx + ) + dotenv_as_dict = { + k: v + for (k, v) in dotenv_values(file).items() + if v is not None and (override or k not in os.environ) + } + + if not commandline: + click.echo('No command given.') + exit(1) + ret = run_command(commandline, dotenv_as_dict) + exit(ret) + + +def run_command(command: List[str], env: Dict[str, str]) -> int: + """Run command in sub process. + + Runs the command in a sub process with the variables from `env` + added in the current environment variables. + + Parameters + ---------- + command: List[str] + The command and it's parameters + env: Dict + The additional environment variables + + Returns + ------- + int + The return code of the command + + """ + # copy the current environment variables and add the vales from + # `env` + cmd_env = os.environ.copy() + cmd_env.update(env) + + p = Popen(command, + universal_newlines=True, + bufsize=0, + shell=False, + env=cmd_env) + _, _ = p.communicate() + + return p.returncode + + +if __name__ == "__main__": + cli() diff --git a/venv/Lib/site-packages/dotenv/ipython.py b/venv/Lib/site-packages/dotenv/ipython.py new file mode 100644 index 0000000..7df727c --- /dev/null +++ b/venv/Lib/site-packages/dotenv/ipython.py @@ -0,0 +1,39 @@ +from IPython.core.magic import Magics, line_magic, magics_class # type: ignore +from IPython.core.magic_arguments import (argument, magic_arguments, # type: ignore + parse_argstring) # type: ignore + +from .main import find_dotenv, load_dotenv + + +@magics_class +class IPythonDotEnv(Magics): + + @magic_arguments() + @argument( + '-o', '--override', action='store_true', + help="Indicate to override existing variables" + ) + @argument( + '-v', '--verbose', action='store_true', + help="Indicate function calls to be verbose" + ) + @argument('dotenv_path', nargs='?', type=str, default='.env', + help='Search in increasingly higher folders for the `dotenv_path`') + @line_magic + def dotenv(self, line): + args = parse_argstring(self.dotenv, line) + # Locate the .env file + dotenv_path = args.dotenv_path + try: + dotenv_path = find_dotenv(dotenv_path, True, True) + except IOError: + print("cannot find .env file") + return + + # Load the .env file + load_dotenv(dotenv_path, verbose=args.verbose, override=args.override) + + +def load_ipython_extension(ipython): + """Register the %dotenv magic.""" + ipython.register_magics(IPythonDotEnv) diff --git a/venv/Lib/site-packages/dotenv/main.py b/venv/Lib/site-packages/dotenv/main.py new file mode 100644 index 0000000..20ac61b --- /dev/null +++ b/venv/Lib/site-packages/dotenv/main.py @@ -0,0 +1,373 @@ +import io +import logging +import os +import shutil +import sys +import tempfile +from collections import OrderedDict +from contextlib import contextmanager +from typing import (IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, + Union) + +from .parser import Binding, parse_stream +from .variables import parse_variables + +logger = logging.getLogger(__name__) + +if sys.version_info >= (3, 6): + _PathLike = os.PathLike +else: + _PathLike = str + + +def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]: + for mapping in mappings: + if mapping.error: + logger.warning( + "Python-dotenv could not parse statement starting at line %s", + mapping.original.line, + ) + yield mapping + + +class DotEnv(): + def __init__( + self, + dotenv_path: Optional[Union[str, _PathLike]], + stream: Optional[IO[str]] = None, + verbose: bool = False, + encoding: Union[None, str] = None, + interpolate: bool = True, + override: bool = True, + ) -> None: + self.dotenv_path = dotenv_path # type: Optional[Union[str, _PathLike]] + self.stream = stream # type: Optional[IO[str]] + self._dict = None # type: Optional[Dict[str, Optional[str]]] + self.verbose = verbose # type: bool + self.encoding = encoding # type: Union[None, str] + self.interpolate = interpolate # type: bool + self.override = override # type: bool + + @contextmanager + def _get_stream(self) -> Iterator[IO[str]]: + if self.dotenv_path and os.path.isfile(self.dotenv_path): + with io.open(self.dotenv_path, encoding=self.encoding) as stream: + yield stream + elif self.stream is not None: + yield self.stream + else: + if self.verbose: + logger.info( + "Python-dotenv could not find configuration file %s.", + self.dotenv_path or '.env', + ) + yield io.StringIO('') + + def dict(self) -> Dict[str, Optional[str]]: + """Return dotenv as dict""" + if self._dict: + return self._dict + + raw_values = self.parse() + + if self.interpolate: + self._dict = OrderedDict(resolve_variables(raw_values, override=self.override)) + else: + self._dict = OrderedDict(raw_values) + + return self._dict + + def parse(self) -> Iterator[Tuple[str, Optional[str]]]: + with self._get_stream() as stream: + for mapping in with_warn_for_invalid_lines(parse_stream(stream)): + if mapping.key is not None: + yield mapping.key, mapping.value + + def set_as_environment_variables(self) -> bool: + """ + Load the current dotenv as system environment variable. + """ + for k, v in self.dict().items(): + if k in os.environ and not self.override: + continue + if v is not None: + os.environ[k] = v + + return True + + def get(self, key: str) -> Optional[str]: + """ + """ + data = self.dict() + + if key in data: + return data[key] + + if self.verbose: + logger.warning("Key %s not found in %s.", key, self.dotenv_path) + + return None + + +def get_key( + dotenv_path: Union[str, _PathLike], + key_to_get: str, + encoding: Optional[str] = "utf-8", +) -> Optional[str]: + """ + Get the value of a given key from the given .env. + + Returns `None` if the key isn't found or doesn't have a value. + """ + return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get) + + +@contextmanager +def rewrite( + path: Union[str, _PathLike], + encoding: Optional[str], +) -> Iterator[Tuple[IO[str], IO[str]]]: + try: + if not os.path.isfile(path): + with io.open(path, "w+", encoding=encoding) as source: + source.write("") + with tempfile.NamedTemporaryFile(mode="w+", delete=False, encoding=encoding) as dest: + with io.open(path, encoding=encoding) as source: + yield (source, dest) # type: ignore + except BaseException: + if os.path.isfile(dest.name): + os.unlink(dest.name) + raise + else: + shutil.move(dest.name, path) + + +def set_key( + dotenv_path: Union[str, _PathLike], + key_to_set: str, + value_to_set: str, + quote_mode: str = "always", + export: bool = False, + encoding: Optional[str] = "utf-8", +) -> Tuple[Optional[bool], str, str]: + """ + Adds or Updates a key/value to the given .env + + If the .env path given doesn't exist, fails instead of risking creating + an orphan .env somewhere in the filesystem + """ + if quote_mode not in ("always", "auto", "never"): + raise ValueError("Unknown quote_mode: {}".format(quote_mode)) + + quote = ( + quote_mode == "always" + or (quote_mode == "auto" and not value_to_set.isalnum()) + ) + + if quote: + value_out = "'{}'".format(value_to_set.replace("'", "\\'")) + else: + value_out = value_to_set + if export: + line_out = 'export {}={}\n'.format(key_to_set, value_out) + else: + line_out = "{}={}\n".format(key_to_set, value_out) + + with rewrite(dotenv_path, encoding=encoding) as (source, dest): + replaced = False + missing_newline = False + for mapping in with_warn_for_invalid_lines(parse_stream(source)): + if mapping.key == key_to_set: + dest.write(line_out) + replaced = True + else: + dest.write(mapping.original.string) + missing_newline = not mapping.original.string.endswith("\n") + if not replaced: + if missing_newline: + dest.write("\n") + dest.write(line_out) + + return True, key_to_set, value_to_set + + +def unset_key( + dotenv_path: Union[str, _PathLike], + key_to_unset: str, + quote_mode: str = "always", + encoding: Optional[str] = "utf-8", +) -> Tuple[Optional[bool], str]: + """ + Removes a given key from the given .env + + If the .env path given doesn't exist, fails + If the given key doesn't exist in the .env, fails + """ + if not os.path.exists(dotenv_path): + logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path) + return None, key_to_unset + + removed = False + with rewrite(dotenv_path, encoding=encoding) as (source, dest): + for mapping in with_warn_for_invalid_lines(parse_stream(source)): + if mapping.key == key_to_unset: + removed = True + else: + dest.write(mapping.original.string) + + if not removed: + logger.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path) + return None, key_to_unset + + return removed, key_to_unset + + +def resolve_variables( + values: Iterable[Tuple[str, Optional[str]]], + override: bool, +) -> Mapping[str, Optional[str]]: + new_values = {} # type: Dict[str, Optional[str]] + + for (name, value) in values: + if value is None: + result = None + else: + atoms = parse_variables(value) + env = {} # type: Dict[str, Optional[str]] + if override: + env.update(os.environ) # type: ignore + env.update(new_values) + else: + env.update(new_values) + env.update(os.environ) # type: ignore + result = "".join(atom.resolve(env) for atom in atoms) + + new_values[name] = result + + return new_values + + +def _walk_to_root(path: str) -> Iterator[str]: + """ + Yield directories starting from the given directory up to the root + """ + if not os.path.exists(path): + raise IOError('Starting path not found') + + if os.path.isfile(path): + path = os.path.dirname(path) + + last_dir = None + current_dir = os.path.abspath(path) + while last_dir != current_dir: + yield current_dir + parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir)) + last_dir, current_dir = current_dir, parent_dir + + +def find_dotenv( + filename: str = '.env', + raise_error_if_not_found: bool = False, + usecwd: bool = False, +) -> str: + """ + Search in increasingly higher folders for the given file + + Returns path to the file if found, or an empty string otherwise + """ + + def _is_interactive(): + """ Decide whether this is running in a REPL or IPython notebook """ + main = __import__('__main__', None, None, fromlist=['__file__']) + return not hasattr(main, '__file__') + + if usecwd or _is_interactive() or getattr(sys, 'frozen', False): + # Should work without __file__, e.g. in REPL or IPython notebook. + path = os.getcwd() + else: + # will work for .py files + frame = sys._getframe() + current_file = __file__ + + while frame.f_code.co_filename == current_file: + assert frame.f_back is not None + frame = frame.f_back + frame_filename = frame.f_code.co_filename + path = os.path.dirname(os.path.abspath(frame_filename)) + + for dirname in _walk_to_root(path): + check_path = os.path.join(dirname, filename) + if os.path.isfile(check_path): + return check_path + + if raise_error_if_not_found: + raise IOError('File not found') + + return '' + + +def load_dotenv( + dotenv_path: Union[str, _PathLike, None] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + override: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> bool: + """Parse a .env file and then load all the variables found as environment variables. + + - *dotenv_path*: absolute or relative path to .env file. + - *stream*: Text stream (such as `io.StringIO`) with .env content, used if + `dotenv_path` is `None`. + - *verbose*: whether to output a warning the .env file is missing. Defaults to + `False`. + - *override*: whether to override the system environment variables with the variables + in `.env` file. Defaults to `False`. + - *encoding*: encoding to be used to read the file. + + If both `dotenv_path` and `stream`, `find_dotenv()` is used to find the .env file. + """ + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + dotenv = DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=override, + encoding=encoding, + ) + return dotenv.set_as_environment_variables() + + +def dotenv_values( + dotenv_path: Union[str, _PathLike, None] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> Dict[str, Optional[str]]: + """ + Parse a .env file and return its content as a dict. + + - *dotenv_path*: absolute or relative path to .env file. + - *stream*: `StringIO` object with .env content, used if `dotenv_path` is `None`. + - *verbose*: whether to output a warning the .env file is missing. Defaults to + `False`. + in `.env` file. Defaults to `False`. + - *encoding*: encoding to be used to read the file. + + If both `dotenv_path` and `stream`, `find_dotenv()` is used to find the .env file. + """ + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + return DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=True, + encoding=encoding, + ).dict() diff --git a/venv/Lib/site-packages/dotenv/parser.py b/venv/Lib/site-packages/dotenv/parser.py new file mode 100644 index 0000000..398bd49 --- /dev/null +++ b/venv/Lib/site-packages/dotenv/parser.py @@ -0,0 +1,182 @@ +import codecs +import re +from typing import (IO, Iterator, Match, NamedTuple, Optional, # noqa:F401 + Pattern, Sequence, Tuple) + + +def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]: + return re.compile(string, re.UNICODE | extra_flags) + + +_newline = make_regex(r"(\r\n|\n|\r)") +_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE) +_whitespace = make_regex(r"[^\S\r\n]*") +_export = make_regex(r"(?:export[^\S\r\n]+)?") +_single_quoted_key = make_regex(r"'([^']+)'") +_unquoted_key = make_regex(r"([^=\#\s]+)") +_equal_sign = make_regex(r"(=[^\S\r\n]*)") +_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'") +_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"') +_unquoted_value = make_regex(r"([^\r\n]*)") +_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?") +_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)") +_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?") +_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]") +_single_quote_escapes = make_regex(r"\\[\\']") + + +Original = NamedTuple( + "Original", + [ + ("string", str), + ("line", int), + ], +) + +Binding = NamedTuple( + "Binding", + [ + ("key", Optional[str]), + ("value", Optional[str]), + ("original", Original), + ("error", bool), + ], +) + + +class Position: + def __init__(self, chars: int, line: int) -> None: + self.chars = chars + self.line = line + + @classmethod + def start(cls) -> "Position": + return cls(chars=0, line=1) + + def set(self, other: "Position") -> None: + self.chars = other.chars + self.line = other.line + + def advance(self, string: str) -> None: + self.chars += len(string) + self.line += len(re.findall(_newline, string)) + + +class Error(Exception): + pass + + +class Reader: + def __init__(self, stream: IO[str]) -> None: + self.string = stream.read() + self.position = Position.start() + self.mark = Position.start() + + def has_next(self) -> bool: + return self.position.chars < len(self.string) + + def set_mark(self) -> None: + self.mark.set(self.position) + + def get_marked(self) -> Original: + return Original( + string=self.string[self.mark.chars:self.position.chars], + line=self.mark.line, + ) + + def peek(self, count: int) -> str: + return self.string[self.position.chars:self.position.chars + count] + + def read(self, count: int) -> str: + result = self.string[self.position.chars:self.position.chars + count] + if len(result) < count: + raise Error("read: End of string") + self.position.advance(result) + return result + + def read_regex(self, regex: Pattern[str]) -> Sequence[str]: + match = regex.match(self.string, self.position.chars) + if match is None: + raise Error("read_regex: Pattern not found") + self.position.advance(self.string[match.start():match.end()]) + return match.groups() + + +def decode_escapes(regex: Pattern[str], string: str) -> str: + def decode_match(match: Match[str]) -> str: + return codecs.decode(match.group(0), 'unicode-escape') # type: ignore + + return regex.sub(decode_match, string) + + +def parse_key(reader: Reader) -> Optional[str]: + char = reader.peek(1) + if char == "#": + return None + elif char == "'": + (key,) = reader.read_regex(_single_quoted_key) + else: + (key,) = reader.read_regex(_unquoted_key) + return key + + +def parse_unquoted_value(reader: Reader) -> str: + (part,) = reader.read_regex(_unquoted_value) + return re.sub(r"\s+#.*", "", part).rstrip() + + +def parse_value(reader: Reader) -> str: + char = reader.peek(1) + if char == u"'": + (value,) = reader.read_regex(_single_quoted_value) + return decode_escapes(_single_quote_escapes, value) + elif char == u'"': + (value,) = reader.read_regex(_double_quoted_value) + return decode_escapes(_double_quote_escapes, value) + elif char in (u"", u"\n", u"\r"): + return u"" + else: + return parse_unquoted_value(reader) + + +def parse_binding(reader: Reader) -> Binding: + reader.set_mark() + try: + reader.read_regex(_multiline_whitespace) + if not reader.has_next(): + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=False, + ) + reader.read_regex(_export) + key = parse_key(reader) + reader.read_regex(_whitespace) + if reader.peek(1) == "=": + reader.read_regex(_equal_sign) + value = parse_value(reader) # type: Optional[str] + else: + value = None + reader.read_regex(_comment) + reader.read_regex(_end_of_line) + return Binding( + key=key, + value=value, + original=reader.get_marked(), + error=False, + ) + except Error: + reader.read_regex(_rest_of_line) + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=True, + ) + + +def parse_stream(stream: IO[str]) -> Iterator[Binding]: + reader = Reader(stream) + while reader.has_next(): + yield parse_binding(reader) diff --git a/venv/Lib/site-packages/dotenv/py.typed b/venv/Lib/site-packages/dotenv/py.typed new file mode 100644 index 0000000..7632ecf --- /dev/null +++ b/venv/Lib/site-packages/dotenv/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/venv/Lib/site-packages/dotenv/variables.py b/venv/Lib/site-packages/dotenv/variables.py new file mode 100644 index 0000000..d77b700 --- /dev/null +++ b/venv/Lib/site-packages/dotenv/variables.py @@ -0,0 +1,88 @@ +import re +from abc import ABCMeta +from typing import Iterator, Mapping, Optional, Pattern + +_posix_variable = re.compile( + r""" + \$\{ + (?P[^\}:]*) + (?::- + (?P[^\}]*) + )? + \} + """, + re.VERBOSE, +) # type: Pattern[str] + + +class Atom(): + __metaclass__ = ABCMeta + + def __ne__(self, other: object) -> bool: + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + return not result + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + raise NotImplementedError + + +class Literal(Atom): + def __init__(self, value: str) -> None: + self.value = value + + def __repr__(self) -> str: + return "Literal(value={})".format(self.value) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return self.value == other.value + + def __hash__(self) -> int: + return hash((self.__class__, self.value)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + return self.value + + +class Variable(Atom): + def __init__(self, name: str, default: Optional[str]) -> None: + self.name = name + self.default = default + + def __repr__(self) -> str: + return "Variable(name={}, default={})".format(self.name, self.default) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return (self.name, self.default) == (other.name, other.default) + + def __hash__(self) -> int: + return hash((self.__class__, self.name, self.default)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + default = self.default if self.default is not None else "" + result = env.get(self.name, default) + return result if result is not None else "" + + +def parse_variables(value: str) -> Iterator[Atom]: + cursor = 0 + + for match in _posix_variable.finditer(value): + (start, end) = match.span() + name = match.groupdict()["name"] + default = match.groupdict()["default"] + + if start > cursor: + yield Literal(value=value[cursor:start]) + + yield Variable(name=name, default=default) + cursor = end + + length = len(value) + if cursor < length: + yield Literal(value=value[cursor:length]) diff --git a/venv/Lib/site-packages/dotenv/version.py b/venv/Lib/site-packages/dotenv/version.py new file mode 100644 index 0000000..5f4bb0b --- /dev/null +++ b/venv/Lib/site-packages/dotenv/version.py @@ -0,0 +1 @@ +__version__ = "0.20.0" diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/DESCRIPTION.rst b/venv/Lib/site-packages/pyserial-3.5.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..606dcaa --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/DESCRIPTION.rst @@ -0,0 +1,13 @@ +Python Serial Port Extension for Win32, OSX, Linux, BSD, Jython, IronPython + +Stable: + +- Documentation: http://pythonhosted.org/pyserial/ +- Download Page: https://pypi.python.org/pypi/pyserial + +Latest: + +- Documentation: http://pyserial.readthedocs.io/en/latest/ +- Project Homepage: https://github.com/pyserial/pyserial + + diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/INSTALLER b/venv/Lib/site-packages/pyserial-3.5.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/METADATA b/venv/Lib/site-packages/pyserial-3.5.dist-info/METADATA new file mode 100644 index 0000000..363d8e1 --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/METADATA @@ -0,0 +1,47 @@ +Metadata-Version: 2.0 +Name: pyserial +Version: 3.5 +Summary: Python Serial Port Extension +Home-page: https://github.com/pyserial/pyserial +Author: Chris Liechti +Author-email: cliechti@gmx.net +License: BSD +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: License :: OSI Approved :: BSD License +Classifier: Natural Language :: English +Classifier: Operating System :: POSIX +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS :: MacOS X +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: Topic :: Communications +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Terminals :: Serial +Provides-Extra: cp2110 +Provides-Extra: cp2110 +Requires-Dist: hidapi; extra == 'cp2110' + +Python Serial Port Extension for Win32, OSX, Linux, BSD, Jython, IronPython + +Stable: + +- Documentation: http://pythonhosted.org/pyserial/ +- Download Page: https://pypi.python.org/pypi/pyserial + +Latest: + +- Documentation: http://pyserial.readthedocs.io/en/latest/ +- Project Homepage: https://github.com/pyserial/pyserial + + diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/RECORD b/venv/Lib/site-packages/pyserial-3.5.dist-info/RECORD new file mode 100644 index 0000000..c0ec5e2 --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/RECORD @@ -0,0 +1,67 @@ +../../Scripts/pyserial-miniterm.exe,sha256=AcbSFvlR6K0gFKBkBcPcYPIc6xThaiCXGI5mCw8APnw,106375 +../../Scripts/pyserial-ports.exe,sha256=5K19acNqjlAfPxyltUEZlsX5oMUa_eTef1xnVVRaQb4,106377 +pyserial-3.5.dist-info/DESCRIPTION.rst,sha256=rXXIUFeAsfXq2YS7DGkztGmXez-G7gAwbwdBL8t9KME,320 +pyserial-3.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyserial-3.5.dist-info/METADATA,sha256=QqirfpTvC3uqfpTNrGXWuSVMYIR29jASDJkAB79HKUM,1650 +pyserial-3.5.dist-info/RECORD,, +pyserial-3.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyserial-3.5.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110 +pyserial-3.5.dist-info/entry_points.txt,sha256=-AQ3oVmIn7rtW5Dh0Oup90Hq0qkIlMj79qGmdDIXk9U,112 +pyserial-3.5.dist-info/metadata.json,sha256=s5rFXxQKL9QXO3UMXRmYoMXGRQt2lol67rf_64S1v10,1647 +pyserial-3.5.dist-info/top_level.txt,sha256=FSjfWHWw-VjPiEqOhttbiP-F8OHn-liixq1wKL2fWOA,7 +serial/__init__.py,sha256=XeyJf970Wg6vY-rNoeAdYuHnVJAwJYSuAjj3U3ZZI0Q,3212 +serial/__main__.py,sha256=oSpVknDS2Yqn2JdXlDs5Fk0E8ccdiLIJaXvPWUizQj0,45 +serial/__pycache__/__init__.cpython-39.pyc,, +serial/__pycache__/__main__.cpython-39.pyc,, +serial/__pycache__/rfc2217.cpython-39.pyc,, +serial/__pycache__/rs485.cpython-39.pyc,, +serial/__pycache__/serialcli.cpython-39.pyc,, +serial/__pycache__/serialjava.cpython-39.pyc,, +serial/__pycache__/serialposix.cpython-39.pyc,, +serial/__pycache__/serialutil.cpython-39.pyc,, +serial/__pycache__/serialwin32.cpython-39.pyc,, +serial/__pycache__/win32.cpython-39.pyc,, +serial/rfc2217.py,sha256=ncG_5Ts42M_Tm_7XN3Q7iE24y-lGcwu2jC3MFSEv6Bc,59700 +serial/rs485.py,sha256=9t6yuGcte36gk8G1U6NgboKVGtJUFqtbpAOXj7vYxM0,3305 +serial/serialcli.py,sha256=u5QnG90UxttqsGG9nYgkj0GUyb0wIOxzlUgxJ4gCczg,9190 +serial/serialjava.py,sha256=AcHLp2D_sAihu7L_wCcg8mtk7etf6zAyB4L_tuthVo8,8480 +serial/serialposix.py,sha256=XVb5hRM5HhdmoYR6BOLhICztQXKhUoA7ocgjoUmptvk,35127 +serial/serialutil.py,sha256=PIT4x8MZ8WGoXW-Ntb7cT2UlVxRYm9y-7m8tr2WhfAo,21797 +serial/serialwin32.py,sha256=F2geqaZQEgxx2xqum4iBMBpA04xHs0uw2gUOP1v7vgA,20284 +serial/threaded/__init__.py,sha256=ikXlKYejRlzzCze9kwxR_uABKa1YfuTyqcPjw3VRV1I,9319 +serial/threaded/__pycache__/__init__.cpython-39.pyc,, +serial/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +serial/tools/__pycache__/__init__.cpython-39.pyc,, +serial/tools/__pycache__/hexlify_codec.cpython-39.pyc,, +serial/tools/__pycache__/list_ports.cpython-39.pyc,, +serial/tools/__pycache__/list_ports_common.cpython-39.pyc,, +serial/tools/__pycache__/list_ports_linux.cpython-39.pyc,, +serial/tools/__pycache__/list_ports_osx.cpython-39.pyc,, +serial/tools/__pycache__/list_ports_posix.cpython-39.pyc,, +serial/tools/__pycache__/list_ports_windows.cpython-39.pyc,, +serial/tools/__pycache__/miniterm.cpython-39.pyc,, +serial/tools/hexlify_codec.py,sha256=FRJSO8pfjM6AR9_SBqL34e50LVkvlzfFKdmCScGn408,3677 +serial/tools/list_ports.py,sha256=eDDoyIhoS3f9D3CVpthqlQUqiR2l-X0VTGGOBjuM4ew,3389 +serial/tools/list_ports_common.py,sha256=x5HIghG4NIz-Xf5iX6Gk7xZfdeads2tqCsfyJhh3Ifs,3736 +serial/tools/list_ports_linux.py,sha256=UnU1VYP1NJI7J8Zn7gY-A2mbi1lugbFZSVztfX8P1pU,4503 +serial/tools/list_ports_osx.py,sha256=eoefMGiuJqC-OCu9aAWqqJX75wGlBzoqZ6kdmMA82LM,11178 +serial/tools/list_ports_posix.py,sha256=EYqD5kRbk0f2a5scaRS4tgWGBynkpVH77ja_G6S3UhE,4535 +serial/tools/list_ports_windows.py,sha256=U4EzcOAiU66LWoPdMXI2oOM4LCd5vKwO3DgeTT6M3qc,16021 +serial/tools/miniterm.py,sha256=fXvkEU9FEyU7HPSNE8bdX2OCgiGYgCee066yF58nots,37840 +serial/urlhandler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +serial/urlhandler/__pycache__/__init__.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_alt.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_cp2110.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_hwgrep.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_loop.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_rfc2217.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_socket.cpython-39.pyc,, +serial/urlhandler/__pycache__/protocol_spy.cpython-39.pyc,, +serial/urlhandler/protocol_alt.py,sha256=-kYoCgy9GyMWN6wC8Oew8FL04LjL4Ntx3HHVqSTKGcQ,2033 +serial/urlhandler/protocol_cp2110.py,sha256=iULOT4Vdw20P_w2jfSWdt0roUY1Ku8xJVHHYc6d3ImY,8540 +serial/urlhandler/protocol_hwgrep.py,sha256=GdKdQ9tExKRHJzsiPcQ9ExmaLa6-A71q52i4jQxmoBk,3159 +serial/urlhandler/protocol_loop.py,sha256=5barru_hfwNkayjjBz4w3snBJn0G7C-fG7-QmHaeTWo,10623 +serial/urlhandler/protocol_rfc2217.py,sha256=IPO8r3pFN6yEDl1Zv2jgUnfIa0tQ0iY0ZsD8_xhAUeQ,317 +serial/urlhandler/protocol_socket.py,sha256=QotaHCPd6t903W_9fa2Lv_5uElq0noir2Ci10a987XM,14299 +serial/urlhandler/protocol_spy.py,sha256=FdUaU43-bl1KXTy_S4HhzyGV5hpUsopm8IqTrX2VX-4,9130 +serial/win32.py,sha256=lk6rod9mHkqzgchmaqB3ygiTkmAWTNQ00IJ985ZjvTI,11138 diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/REQUESTED b/venv/Lib/site-packages/pyserial-3.5.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/WHEEL b/venv/Lib/site-packages/pyserial-3.5.dist-info/WHEEL new file mode 100644 index 0000000..7332a41 --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/entry_points.txt b/venv/Lib/site-packages/pyserial-3.5.dist-info/entry_points.txt new file mode 100644 index 0000000..b69a613 --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +pyserial-miniterm = serial.tools.miniterm:main +pyserial-ports = serial.tools.list_ports:main + diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/metadata.json b/venv/Lib/site-packages/pyserial-3.5.dist-info/metadata.json new file mode 100644 index 0000000..5064763 --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Topic :: Communications", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Terminals :: Serial"], "extensions": {"python.commands": {"wrap_console": {"pyserial-miniterm": "serial.tools.miniterm:main", "pyserial-ports": "serial.tools.list_ports:main"}}, "python.details": {"contacts": [{"email": "cliechti@gmx.net", "name": "Chris Liechti", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/pyserial/pyserial"}}, "python.exports": {"console_scripts": {"pyserial-miniterm": "serial.tools.miniterm:main", "pyserial-ports": "serial.tools.list_ports:main"}}}, "extras": ["cp2110"], "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "pyserial", "platform": "any", "run_requires": [{"extra": "cp2110", "requires": ["hidapi"]}], "summary": "Python Serial Port Extension", "version": "3.5"} \ No newline at end of file diff --git a/venv/Lib/site-packages/pyserial-3.5.dist-info/top_level.txt b/venv/Lib/site-packages/pyserial-3.5.dist-info/top_level.txt new file mode 100644 index 0000000..b6be06a --- /dev/null +++ b/venv/Lib/site-packages/pyserial-3.5.dist-info/top_level.txt @@ -0,0 +1 @@ +serial diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/INSTALLER b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/LICENSE b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/LICENSE new file mode 100644 index 0000000..39372fe --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/LICENSE @@ -0,0 +1,87 @@ +python-dotenv +Copyright (c) 2014, Saurabh Kumar + +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 python-dotenv nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +django-dotenv-rw +Copyright (c) 2013, Ted Tieken + +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 django-dotenv nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Original django-dotenv +Copyright (c) 2013, Jacob Kaplan-Moss + +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 django-dotenv nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/METADATA b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/METADATA new file mode 100644 index 0000000..5d16fa8 --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/METADATA @@ -0,0 +1,594 @@ +Metadata-Version: 2.1 +Name: python-dotenv +Version: 0.20.0 +Summary: Read key-value pairs from a .env file and set them as environment variables +Home-page: https://github.com/theskumar/python-dotenv +Author: Saurabh Kumar +Author-email: me+github@saurabh-kumar.com +License: BSD-3-Clause +Keywords: environment variables,deployments,settings,env,dotenv,configurations,python +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +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: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Classifier: Environment :: Web Environment +Requires-Python: >=3.5 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: cli +Requires-Dist: click (>=5.0) ; extra == 'cli' + +# python-dotenv + +[![Build Status][build_status_badge]][build_status_link] +[![PyPI version][pypi_badge]][pypi_link] + +Python-dotenv reads key-value pairs from a `.env` file and can set them as environment +variables. It helps in the development of applications following the +[12-factor](http://12factor.net/) principles. + +- [Getting Started](#getting-started) +- [Other Use Cases](#other-use-cases) + * [Load configuration without altering the environment](#load-configuration-without-altering-the-environment) + * [Parse configuration as a stream](#parse-configuration-as-a-stream) + * [Load .env files in IPython](#load-env-files-in-ipython) +- [Command-line Interface](#command-line-interface) +- [File format](#file-format) + * [Multiline values](#multiline-values) + * [Variable expansion](#variable-expansion) +- [Related Projects](#related-projects) +- [Acknowledgements](#acknowledgements) + +## Getting Started + +```shell +pip install python-dotenv +``` + +If your application takes its configuration from environment variables, like a 12-factor +application, launching it in development is not very practical because you have to set +those environment variables yourself. + +To help you with that, you can add Python-dotenv to your application to make it load the +configuration from a `.env` file when it is present (e.g. in development) while remaining +configurable via the environment: + +```python +from dotenv import load_dotenv + +load_dotenv() # take environment variables from .env. + +# Code of your application, which uses environment variables (e.g. from `os.environ` or +# `os.getenv`) as if they came from the actual environment. +``` + +By default, `load_dotenv` doesn't override existing environment variables. + +To configure the development environment, add a `.env` in the root directory of your +project: + +``` +. +├── .env +└── foo.py +``` + +The syntax of `.env` files supported by python-dotenv is similar to that of Bash: + +```bash +# Development settings +DOMAIN=example.org +ADMIN_EMAIL=admin@${DOMAIN} +ROOT_URL=${DOMAIN}/app +``` + +If you use variables in values, ensure they are surrounded with `{` and `}`, like +`${DOMAIN}`, as bare variables such as `$DOMAIN` are not expanded. + +You will probably want to add `.env` to your `.gitignore`, especially if it contains +secrets like a password. + +See the section "File format" below for more information about what you can write in a +`.env` file. + +## Other Use Cases + +### Load configuration without altering the environment + +The function `dotenv_values` works more or less the same way as `load_dotenv`, except it +doesn't touch the environment, it just returns a `dict` with the values parsed from the +`.env` file. + +```python +from dotenv import dotenv_values + +config = dotenv_values(".env") # config = {"USER": "foo", "EMAIL": "foo@example.org"} +``` + +This notably enables advanced configuration management: + +```python +import os +from dotenv import dotenv_values + +config = { + **dotenv_values(".env.shared"), # load shared development variables + **dotenv_values(".env.secret"), # load sensitive variables + **os.environ, # override loaded values with environment variables +} +``` + +### Parse configuration as a stream + +`load_dotenv` and `dotenv_values` accept [streams][python_streams] via their `stream` +argument. It is thus possible to load the variables from sources other than the +filesystem (e.g. the network). + +```python +from io import StringIO + +from dotenv import load_dotenv + +config = StringIO("USER=foo\nEMAIL=foo@example.org") +load_dotenv(stream=config) +``` + +### Load .env files in IPython + +You can use dotenv in IPython. By default, it will use `find_dotenv` to search for a +`.env` file: + +```python +%load_ext dotenv +%dotenv +``` + +You can also specify a path: + +```python +%dotenv relative/or/absolute/path/to/.env +``` + +Optional flags: + +- `-o` to override existing variables. +- `-v` for increased verbosity. + +## Command-line Interface + +A CLI interface `dotenv` is also included, which helps you manipulate the `.env` file +without manually opening it. + +```shell +$ pip install "python-dotenv[cli]" +$ dotenv set USER foo +$ dotenv set EMAIL foo@example.org +$ dotenv list +USER=foo +EMAIL=foo@example.org +$ dotenv run -- python foo.py +``` + +Run `dotenv --help` for more information about the options and subcommands. + +## File format + +The format is not formally specified and still improves over time. That being said, +`.env` files should mostly look like Bash files. + +Keys can be unquoted or single-quoted. Values can be unquoted, single- or double-quoted. +Spaces before and after keys, equal signs, and values are ignored. Values can be followed +by a comment. Lines can start with the `export` directive, which has no effect on their +interpretation. + +Allowed escape sequences: + +- in single-quoted values: `\\`, `\'` +- in double-quoted values: `\\`, `\'`, `\"`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v` + +### Multiline values + +It is possible for single- or double-quoted values to span multiple lines. The following +examples are equivalent: + +```bash +FOO="first line +second line" +``` + +```bash +FOO="first line\nsecond line" +``` + +### Variable expansion + +Python-dotenv can interpolate variables using POSIX variable expansion. + +With `load_dotenv(override=True)` or `dotenv_values()`, the value of a variable is the +first of the values defined in the following list: + +- Value of that variable in the `.env` file. +- Value of that variable in the environment. +- Default value, if provided. +- Empty string. + +With `load_dotenv(override=False)`, the value of a variable is the first of the values +defined in the following list: + +- Value of that variable in the environment. +- Value of that variable in the `.env` file. +- Default value, if provided. +- Empty string. + +## Related Projects + +- [Honcho](https://github.com/nickstenning/honcho) - For managing + Procfile-based applications. +- [django-dotenv](https://github.com/jpadilla/django-dotenv) +- [django-environ](https://github.com/joke2k/django-environ) +- [django-environ-2](https://github.com/sergeyklay/django-environ-2) +- [django-configuration](https://github.com/jezdez/django-configurations) +- [dump-env](https://github.com/sobolevn/dump-env) +- [environs](https://github.com/sloria/environs) +- [dynaconf](https://github.com/rochacbruno/dynaconf) + +## Acknowledgements + +This project is currently maintained by [Saurabh Kumar](https://saurabh-kumar.com) and +[Bertrand Bonnefoy-Claudet](https://github.com/bbc2) and would not have been possible +without the support of these [awesome +people](https://github.com/theskumar/python-dotenv/graphs/contributors). + +[build_status_badge]: https://github.com/theskumar/python-dotenv/actions/workflows/test.yml/badge.svg +[build_status_link]: https://github.com/theskumar/python-dotenv/actions/workflows/test.yml +[pypi_badge]: https://badge.fury.io/py/python-dotenv.svg +[pypi_link]: http://badge.fury.io/py/python-dotenv +[python_streams]: https://docs.python.org/3/library/io.html + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this +project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.20.0] - 2022-03-24 + +### Added + +- Add `encoding` (`Optional[str]`) parameter to `get_key`, `set_key` and `unset_key`. + (#379 by [@bbc2]) + +### Fixed + +- Use dict to specify the `entry_points` parameter of `setuptools.setup` (#376 by + [@mgorny]). +- Don't build universal wheels (#387 by [@bbc2]). + +## [0.19.2] - 2021-11-11 + +### Fixed + +- In `set_key`, add missing newline character before new entry if necessary. (#361 by + [@bbc2]) + +## [0.19.1] - 2021-08-09 + +### Added + +- Add support for Python 3.10. (#359 by [@theskumar]) + +## [0.19.0] - 2021-07-24 + +### Changed + +- Require Python 3.5 or a later version. Python 2 and 3.4 are no longer supported. (#341 + by [@bbc2]). + +### Added + +- The `dotenv_path` argument of `set_key` and `unset_key` now has a type of `Union[str, + os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]). +- The `stream` argument of `load_dotenv` and `dotenv_values` can now be a text stream + (`IO[str]`), which includes values like `io.StringIO("foo")` and `open("file.env", + "r")` (#348 by [@bbc2]). + +## [0.18.0] - 2021-06-20 + +### Changed + +- Raise `ValueError` if `quote_mode` isn't one of `always`, `auto` or `never` in + `set_key` (#330 by [@bbc2]). +- When writing a value to a .env file with `set_key` or `dotenv set ` (#330 + by [@bbc2]): + - Use single quotes instead of double quotes. + - Don't strip surrounding quotes. + - In `auto` mode, don't add quotes if the value is only made of alphanumeric characters + (as determined by `string.isalnum`). + +## [0.17.1] - 2021-04-29 + +### Fixed + +- Fixed tests for build environments relying on `PYTHONPATH` (#318 by [@befeleme]). + +## [0.17.0] - 2021-04-02 + +### Changed + +- Make `dotenv get ` only show the value, not `key=value` (#313 by [@bbc2]). + +### Added + +- Add `--override`/`--no-override` option to `dotenv run` (#312 by [@zueve] and [@bbc2]). + +## [0.16.0] - 2021-03-27 + +### Changed + +- The default value of the `encoding` parameter for `load_dotenv` and `dotenv_values` is + now `"utf-8"` instead of `None` (#306 by [@bbc2]). +- Fix resolution order in variable expansion with `override=False` (#287 by [@bbc2]). + +## [0.15.0] - 2020-10-28 + +### Added + +- Add `--export` option to `set` to make it prepend the binding with `export` (#270 by + [@jadutter]). + +### Changed + +- Make `set` command create the `.env` file in the current directory if no `.env` file was + found (#270 by [@jadutter]). + +### Fixed + +- Fix potentially empty expanded value for duplicate key (#260 by [@bbc2]). +- Fix import error on Python 3.5.0 and 3.5.1 (#267 by [@gongqingkui]). +- Fix parsing of unquoted values containing several adjacent space or tab characters + (#277 by [@bbc2], review by [@x-yuri]). + +## [0.14.0] - 2020-07-03 + +### Changed + +- Privilege definition in file over the environment in variable expansion (#256 by + [@elbehery95]). + +### Fixed + +- Improve error message for when file isn't found (#245 by [@snobu]). +- Use HTTPS URL in package meta data (#251 by [@ekohl]). + +## [0.13.0] - 2020-04-16 + +### Added + +- Add support for a Bash-like default value in variable expansion (#248 by [@bbc2]). + +## [0.12.0] - 2020-02-28 + +### Changed + +- Use current working directory to find `.env` when bundled by PyInstaller (#213 by + [@gergelyk]). + +### Fixed + +- Fix escaping of quoted values written by `set_key` (#236 by [@bbc2]). +- Fix `dotenv run` crashing on environment variables without values (#237 by [@yannham]). +- Remove warning when last line is empty (#238 by [@bbc2]). + +## [0.11.0] - 2020-02-07 + +### Added + +- Add `interpolate` argument to `load_dotenv` and `dotenv_values` to disable interpolation + (#232 by [@ulyssessouza]). + +### Changed + +- Use logging instead of warnings (#231 by [@bbc2]). + +### Fixed + +- Fix installation in non-UTF-8 environments (#225 by [@altendky]). +- Fix PyPI classifiers (#228 by [@bbc2]). + +## [0.10.5] - 2020-01-19 + +### Fixed + +- Fix handling of malformed lines and lines without a value (#222 by [@bbc2]): + - Don't print warning when key has no value. + - Reject more malformed lines (e.g. "A: B", "a='b',c"). +- Fix handling of lines with just a comment (#224 by [@bbc2]). + +## [0.10.4] - 2020-01-17 + +### Added + +- Make typing optional (#179 by [@techalchemy]). +- Print a warning on malformed line (#211 by [@bbc2]). +- Support keys without a value (#220 by [@ulyssessouza]). + +## 0.10.3 + +- Improve interactive mode detection ([@andrewsmith])([#183]). +- Refactor parser to fix parsing inconsistencies ([@bbc2])([#170]). + - Interpret escapes as control characters only in double-quoted strings. + - Interpret `#` as start of comment only if preceded by whitespace. + +## 0.10.2 + +- Add type hints and expose them to users ([@qnighy])([#172]) +- `load_dotenv` and `dotenv_values` now accept an `encoding` parameter, defaults to `None` + ([@theskumar])([@earlbread])([#161]) +- Fix `str`/`unicode` inconsistency in Python 2: values are always `str` now. ([@bbc2])([#121]) +- Fix Unicode error in Python 2, introduced in 0.10.0. ([@bbc2])([#176]) + +## 0.10.1 +- Fix parsing of variable without a value ([@asyncee])([@bbc2])([#158]) + +## 0.10.0 + +- Add support for UTF-8 in unquoted values ([@bbc2])([#148]) +- Add support for trailing comments ([@bbc2])([#148]) +- Add backslashes support in values ([@bbc2])([#148]) +- Add support for newlines in values ([@bbc2])([#148]) +- Force environment variables to str with Python2 on Windows ([@greyli]) +- Drop Python 3.3 support ([@greyli]) +- Fix stderr/-out/-in redirection ([@venthur]) + + +## 0.9.0 + +- Add `--version` parameter to cli ([@venthur]) +- Enable loading from current directory ([@cjauvin]) +- Add 'dotenv run' command for calling arbitrary shell script with .env ([@venthur]) + +## 0.8.1 + +- Add tests for docs ([@Flimm]) +- Make 'cli' support optional. Use `pip install python-dotenv[cli]`. ([@theskumar]) + +## 0.8.0 + +- `set_key` and `unset_key` only modified the affected file instead of + parsing and re-writing file, this causes comments and other file + entact as it is. +- Add support for `export` prefix in the line. +- Internal refractoring ([@theskumar]) +- Allow `load_dotenv` and `dotenv_values` to work with `StringIO())` ([@alanjds])([@theskumar])([#78]) + +## 0.7.1 + +- Remove hard dependency on iPython ([@theskumar]) + +## 0.7.0 + +- Add support to override system environment variable via .env. + ([@milonimrod](https://github.com/milonimrod)) + ([\#63](https://github.com/theskumar/python-dotenv/issues/63)) +- Disable ".env not found" warning by default + ([@maxkoryukov](https://github.com/maxkoryukov)) + ([\#57](https://github.com/theskumar/python-dotenv/issues/57)) + +## 0.6.5 + +- Add support for special characters `\`. + ([@pjona](https://github.com/pjona)) + ([\#60](https://github.com/theskumar/python-dotenv/issues/60)) + +## 0.6.4 + +- Fix issue with single quotes ([@Flimm]) + ([\#52](https://github.com/theskumar/python-dotenv/issues/52)) + +## 0.6.3 + +- Handle unicode exception in setup.py + ([\#46](https://github.com/theskumar/python-dotenv/issues/46)) + +## 0.6.2 + +- Fix dotenv list command ([@ticosax](https://github.com/ticosax)) +- Add iPython Support + ([@tillahoffmann](https://github.com/tillahoffmann)) + +## 0.6.0 + +- Drop support for Python 2.6 +- Handle escaped characters and newlines in quoted values. (Thanks + [@iameugenejo](https://github.com/iameugenejo)) +- Remove any spaces around unquoted key/value. (Thanks + [@paulochf](https://github.com/paulochf)) +- Added POSIX variable expansion. (Thanks + [@hugochinchilla](https://github.com/hugochinchilla)) + +## 0.5.1 + +- Fix find\_dotenv - it now start search from the file where this + function is called from. + +## 0.5.0 + +- Add `find_dotenv` method that will try to find a `.env` file. + (Thanks [@isms](https://github.com/isms)) + +## 0.4.0 + +- cli: Added `-q/--quote` option to control the behaviour of quotes + around values in `.env`. (Thanks + [@hugochinchilla](https://github.com/hugochinchilla)). +- Improved test coverage. + +[#78]: https://github.com/theskumar/python-dotenv/issues/78 +[#121]: https://github.com/theskumar/python-dotenv/issues/121 +[#148]: https://github.com/theskumar/python-dotenv/issues/148 +[#158]: https://github.com/theskumar/python-dotenv/issues/158 +[#170]: https://github.com/theskumar/python-dotenv/issues/170 +[#172]: https://github.com/theskumar/python-dotenv/issues/172 +[#176]: https://github.com/theskumar/python-dotenv/issues/176 +[#183]: https://github.com/theskumar/python-dotenv/issues/183 +[#359]: https://github.com/theskumar/python-dotenv/issues/359 + +[@Flimm]: https://github.com/Flimm +[@alanjds]: https://github.com/alanjds +[@altendky]: https://github.com/altendky +[@andrewsmith]: https://github.com/andrewsmith +[@asyncee]: https://github.com/asyncee +[@bbc2]: https://github.com/bbc2 +[@befeleme]: https://github.com/befeleme +[@cjauvin]: https://github.com/cjauvin +[@earlbread]: https://github.com/earlbread +[@ekohl]: https://github.com/ekohl +[@elbehery95]: https://github.com/elbehery95 +[@gergelyk]: https://github.com/gergelyk +[@gongqingkui]: https://github.com/gongqingkui +[@greyli]: https://github.com/greyli +[@jadutter]: https://github.com/jadutter +[@mgorny]: https://github.com/mgorny +[@qnighy]: https://github.com/qnighy +[@snobu]: https://github.com/snobu +[@techalchemy]: https://github.com/techalchemy +[@theskumar]: https://github.com/theskumar +[@ulyssessouza]: https://github.com/ulyssessouza +[@venthur]: https://github.com/venthur +[@x-yuri]: https://github.com/x-yuri +[@yannham]: https://github.com/yannham +[@zueve]: https://github.com/zueve + +[Unreleased]: https://github.com/theskumar/python-dotenv/compare/v0.20.0...HEAD +[0.19.2]: https://github.com/theskumar/python-dotenv/compare/v0.19.2...v0.20.0 +[0.19.2]: https://github.com/theskumar/python-dotenv/compare/v0.19.1...v0.19.2 +[0.19.1]: https://github.com/theskumar/python-dotenv/compare/v0.19.0...v0.19.1 +[0.19.0]: https://github.com/theskumar/python-dotenv/compare/v0.18.0...v0.19.0 +[0.18.0]: https://github.com/theskumar/python-dotenv/compare/v0.17.1...v0.18.0 +[0.17.1]: https://github.com/theskumar/python-dotenv/compare/v0.17.0...v0.17.1 +[0.17.0]: https://github.com/theskumar/python-dotenv/compare/v0.16.0...v0.17.0 +[0.16.0]: https://github.com/theskumar/python-dotenv/compare/v0.15.0...v0.16.0 +[0.15.0]: https://github.com/theskumar/python-dotenv/compare/v0.14.0...v0.15.0 +[0.14.0]: https://github.com/theskumar/python-dotenv/compare/v0.13.0...v0.14.0 +[0.13.0]: https://github.com/theskumar/python-dotenv/compare/v0.12.0...v0.13.0 +[0.12.0]: https://github.com/theskumar/python-dotenv/compare/v0.11.0...v0.12.0 +[0.11.0]: https://github.com/theskumar/python-dotenv/compare/v0.10.5...v0.11.0 +[0.10.5]: https://github.com/theskumar/python-dotenv/compare/v0.10.4...v0.10.5 +[0.10.4]: https://github.com/theskumar/python-dotenv/compare/v0.10.3...v0.10.4 + + diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/RECORD b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/RECORD new file mode 100644 index 0000000..3372cb6 --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/RECORD @@ -0,0 +1,23 @@ +../../Scripts/dotenv.exe,sha256=PPxAaEOeHoHinpldJfQly5oxmRErKMOD1yyQODwaFso,106362 +dotenv/__init__.py,sha256=bdAGaaBAOc_Hyqqy8UOVjedJDgUNqMTLlmqcOrMCdC8,1298 +dotenv/__pycache__/__init__.cpython-39.pyc,, +dotenv/__pycache__/cli.cpython-39.pyc,, +dotenv/__pycache__/ipython.cpython-39.pyc,, +dotenv/__pycache__/main.cpython-39.pyc,, +dotenv/__pycache__/parser.cpython-39.pyc,, +dotenv/__pycache__/variables.cpython-39.pyc,, +dotenv/__pycache__/version.cpython-39.pyc,, +dotenv/cli.py,sha256=MfmIEYbhDhANP7CzAkgDJUahjKcY5mOjCbDEnJNvqhI,4777 +dotenv/ipython.py,sha256=avI6aez_RxnBptYgchIquF2TSgKI-GOhY3ppiu3VuWE,1303 +dotenv/main.py,sha256=wBUr-lDnDo7fKAi8oHJLxRlna4wvoGTgJdJNTikrGw0,11692 +dotenv/parser.py,sha256=HMB0VVy_fejMuGZ_Fdigzln_dBE9nsAAaZ7olwoAZSw,5298 +dotenv/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 +dotenv/variables.py,sha256=HtvYMOOyyogGGZv-YkGzeMOKtovvqqGzh_kIB9xuAto,2404 +dotenv/version.py,sha256=NGIecTe1EEM7UeBjKSJ4vCWuGDWF1ZX4PckW2Eguxps,23 +python_dotenv-0.20.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +python_dotenv-0.20.0.dist-info/LICENSE,sha256=0nIJqz0WJ4Ko-OOHK5s1PEngksmqRnpkUiiDQH2NEDA,4600 +python_dotenv-0.20.0.dist-info/METADATA,sha256=fPA8H1g_zWafVdlx7u-TCw3mFyBDJxZh6JmuOdJDxY0,19376 +python_dotenv-0.20.0.dist-info/RECORD,, +python_dotenv-0.20.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +python_dotenv-0.20.0.dist-info/entry_points.txt,sha256=Ta6e0xl3qUUz_ZZ1D1YAR6SKDHNGBPLcHqfzyXq4TVk,43 +python_dotenv-0.20.0.dist-info/top_level.txt,sha256=eyqUH4SHJNr6ahOYlxIunTr4XinE8Z5ajWLdrK3r0D8,7 diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/WHEEL b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/entry_points.txt b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/entry_points.txt new file mode 100644 index 0000000..deb9ff4 --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +dotenv = dotenv.cli:cli + diff --git a/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/top_level.txt b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/top_level.txt new file mode 100644 index 0000000..fe7c01a --- /dev/null +++ b/venv/Lib/site-packages/python_dotenv-0.20.0.dist-info/top_level.txt @@ -0,0 +1 @@ +dotenv diff --git a/venv/Lib/site-packages/serial/__init__.py b/venv/Lib/site-packages/serial/__init__.py new file mode 100644 index 0000000..caa4de1 --- /dev/null +++ b/venv/Lib/site-packages/serial/__init__.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# This is a wrapper module for different platform implementations +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import sys +import importlib + +from serial.serialutil import * +#~ SerialBase, SerialException, to_bytes, iterbytes + +__version__ = '3.5' + +VERSION = __version__ + +# pylint: disable=wrong-import-position +if sys.platform == 'cli': + from serial.serialcli import Serial +else: + import os + # chose an implementation, depending on os + if os.name == 'nt': # sys.platform == 'win32': + from serial.serialwin32 import Serial + elif os.name == 'posix': + from serial.serialposix import Serial, PosixPollSerial, VTIMESerial # noqa + elif os.name == 'java': + from serial.serialjava import Serial + else: + raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) + + +protocol_handler_packages = [ + 'serial.urlhandler', +] + + +def serial_for_url(url, *args, **kwargs): + """\ + Get an instance of the Serial class, depending on port/url. The port is not + opened when the keyword parameter 'do_not_open' is true, by default it + is. All other parameters are directly passed to the __init__ method when + the port is instantiated. + + The list of package names that is searched for protocol handlers is kept in + ``protocol_handler_packages``. + + e.g. we want to support a URL ``foobar://``. A module + ``my_handlers.protocol_foobar`` is provided by the user. Then + ``protocol_handler_packages.append("my_handlers")`` would extend the search + path so that ``serial_for_url("foobar://"))`` would work. + """ + # check and remove extra parameter to not confuse the Serial class + do_open = not kwargs.pop('do_not_open', False) + # the default is to use the native implementation + klass = Serial + try: + url_lowercase = url.lower() + except AttributeError: + # it's not a string, use default + pass + else: + # if it is an URL, try to import the handler module from the list of possible packages + if '://' in url_lowercase: + protocol = url_lowercase.split('://', 1)[0] + module_name = '.protocol_{}'.format(protocol) + for package_name in protocol_handler_packages: + try: + importlib.import_module(package_name) + handler_module = importlib.import_module(module_name, package_name) + except ImportError: + continue + else: + if hasattr(handler_module, 'serial_class_for_url'): + url, klass = handler_module.serial_class_for_url(url) + else: + klass = handler_module.Serial + break + else: + raise ValueError('invalid URL, protocol {!r} not known'.format(protocol)) + # instantiate and open when desired + instance = klass(None, *args, **kwargs) + instance.port = url + if do_open: + instance.open() + return instance diff --git a/venv/Lib/site-packages/serial/__main__.py b/venv/Lib/site-packages/serial/__main__.py new file mode 100644 index 0000000..bd0a2e6 --- /dev/null +++ b/venv/Lib/site-packages/serial/__main__.py @@ -0,0 +1,3 @@ +from .tools import miniterm + +miniterm.main() diff --git a/venv/Lib/site-packages/serial/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f353f924f31c1403821b2a19f3b66384e0a568d GIT binary patch literal 2168 zcmai0&5s*36t`z4$z+nvZnqy&IhmzsH&C)wD=q~gDz-qS(pKHJw4)cwdmpw#*Va4)Z7*i+ z=T(IMG>enXfyL+WsXw3#QHVJjU>{>Mx47k7*=YMVj5c@fq0sRQtl$@!byx}ueM{~E zr^www*)L-h_cD3U_n3EB2}{5aGk$f+|2m_)Kz|1GGG8$eA93j_$mG#ht)3jPlsz^?HTgVgw{T)ufGHzP#vf1w5xWkAzAiBp$o(a<7RH=(3WJ4B*QQRdEeUt26h--t~13|MQk%BB~&*&2%$wD&j zu{cxIXOppzAsJFh2TU_bwnGtsbYLX6%~mZ(>>_DTz{~+1aZs15id2JK+@y*y@QKM~2HdwZfV^t0Sse*zhv8afL@p8~=y2 z)2CfVVgL;2dNgxTF)BMfuw<%lD1{as!O60?l<}j_h9DVd`dY1(NU#(MmTFb2wY13^ z-3A#mGN!RM_|<5bA*AH~-J7Jw5fe1&OEwv9OE4`}m%2K|e)iPj! zcpQZQ7vgGsAHjjhGWNgt8gyu~y|Z<~##SA)j>U+Fggw^4&2;7=$q)^x?h$2j0MTl- z^3xYUr~;^zovl}{Y}H?Dg1YE8vfX~=L`{4-wZZ3?=D14JO{=dxd@@VVEOlq4xw0_5 z5XHxoN1+Mb#nVmaY8S z3ilG5yE^K>Bl#X6`e*-(tIhimRMi~QZp@l{qBAmgt!j4R$aUGC5YmsjZN{3%EIw}D zjM_~VY4*{opqqI{c2{SwF`T5eH!I*nFr+R+hc;@KgDqUeRjX*bcmsRbg|!E>D)uZG zts*X3HR~KMVh4Egeb`k$n|eWTFw!H*ftDxPEv4#_d}Wu;qi{Z{)I#e1PXFuL(=2)Gb_%}NGOI@A(1t(veJb3pw zj(7KX6wL`DRZu3lUrzkPL$SxPOQf3Av?Y?}loFtK??EW3y^O^Eo1z`lDU?Eq9*jK2 zem?89Zmn?~9 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/__pycache__/rfc2217.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/rfc2217.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcacff0bb26b6da8da0d9b3e89d35cea62835de9 GIT binary patch literal 32560 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 T--9OBudO*fa64hFKm30I$CEcZ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/__pycache__/rs485.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/rs485.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..307cf2439400642cbc822fabc4d4f25a13fbd5f2 GIT binary patch literal 2936 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 D3HEGm literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/__pycache__/serialposix.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/serialposix.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f2bcf7ace70115fbd70b13d66dcf4322711db9d GIT binary patch literal 21827 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/__pycache__/serialutil.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/serialutil.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5e92a3ba30ab105fa75e8645a4e72b6e977f09c GIT binary patch literal 18642 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 Zfsr)+vLmnU{sz9V<{#>EC)peS{a;h%GYkL# literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/__pycache__/serialwin32.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/serialwin32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c90152dbb386aac333596ba5b07900c7a1bcb10 GIT binary patch literal 13113 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 WUJv){jSQYVDl%-rGsqXtss9Z{GC{Qf literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/__pycache__/win32.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/win32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c1aef452b813f14b0f429029175465b837ded5b GIT binary patch literal 6410 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 +# +# SPDX-License-Identifier: BSD-3-Clause + +# TODO: +# - setting control line -> answer is not checked (had problems with one of the +# severs). consider implementing a compatibility mode flag to make check +# conditional +# - write timeout not implemented at all + +# ########################################################################### +# observations and issues with servers +# =========================================================================== +# sredird V2.2.1 +# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz +# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding +# [105 1] instead of the actual value. +# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger +# numbers than 2**32? +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done +# =========================================================================== +# telnetcpcd (untested) +# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz +# - To get the signature [COM_PORT_OPTION] w/o data has to be sent. +# =========================================================================== +# ser2net +# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least +# acknowledges that the client activates these options +# - The configuration may be that the server prints a banner. As this client +# implementation does a flushInput on connect, this banner is hidden from +# the user application. +# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one +# second. +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: run ser2net daemon, in /etc/ser2net.conf: +# 2000:telnet:0:/dev/ttyS0:9600 remctl banner +# ########################################################################### + +# How to identify ports? pySerial might want to support other protocols in the +# future, so lets use an URL scheme. +# for RFC2217 compliant servers we will use this: +# rfc2217://:[?option[&option...]] +# +# options: +# - "logging" set log level print diagnostic messages (e.g. "logging=debug") +# - "ign_set_control": do not look at the answers to SET_CONTROL +# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read. +# Without this option it expects that the server sends notifications +# automatically on change (which most servers do and is according to the +# RFC). +# the order of the options is not relevant + +from __future__ import absolute_import + +import logging +import socket +import struct +import threading +import time +try: + import urlparse +except ImportError: + import urllib.parse as urlparse +try: + import Queue +except ImportError: + import queue as Queue + +import serial +from serial.serialutil import SerialBase, SerialException, to_bytes, \ + iterbytes, PortNotOpenError, Timeout + +# port string is expected to be something like this: +# rfc2217://host:port +# host may be an IP or including domain, whatever. +# port is 0...65535 + +# map log level names to constants. used in from_url() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, +} + + +# telnet protocol characters +SE = b'\xf0' # Subnegotiation End +NOP = b'\xf1' # No Operation +DM = b'\xf2' # Data Mark +BRK = b'\xf3' # Break +IP = b'\xf4' # Interrupt process +AO = b'\xf5' # Abort output +AYT = b'\xf6' # Are You There +EC = b'\xf7' # Erase Character +EL = b'\xf8' # Erase Line +GA = b'\xf9' # Go Ahead +SB = b'\xfa' # Subnegotiation Begin +WILL = b'\xfb' +WONT = b'\xfc' +DO = b'\xfd' +DONT = b'\xfe' +IAC = b'\xff' # Interpret As Command +IAC_DOUBLED = b'\xff\xff' + +# selected telnet options +BINARY = b'\x00' # 8-bit data path +ECHO = b'\x01' # echo +SGA = b'\x03' # suppress go ahead + +# RFC2217 +COM_PORT_OPTION = b'\x2c' + +# Client to Access Server +SET_BAUDRATE = b'\x01' +SET_DATASIZE = b'\x02' +SET_PARITY = b'\x03' +SET_STOPSIZE = b'\x04' +SET_CONTROL = b'\x05' +NOTIFY_LINESTATE = b'\x06' +NOTIFY_MODEMSTATE = b'\x07' +FLOWCONTROL_SUSPEND = b'\x08' +FLOWCONTROL_RESUME = b'\x09' +SET_LINESTATE_MASK = b'\x0a' +SET_MODEMSTATE_MASK = b'\x0b' +PURGE_DATA = b'\x0c' + +SERVER_SET_BAUDRATE = b'\x65' +SERVER_SET_DATASIZE = b'\x66' +SERVER_SET_PARITY = b'\x67' +SERVER_SET_STOPSIZE = b'\x68' +SERVER_SET_CONTROL = b'\x69' +SERVER_NOTIFY_LINESTATE = b'\x6a' +SERVER_NOTIFY_MODEMSTATE = b'\x6b' +SERVER_FLOWCONTROL_SUSPEND = b'\x6c' +SERVER_FLOWCONTROL_RESUME = b'\x6d' +SERVER_SET_LINESTATE_MASK = b'\x6e' +SERVER_SET_MODEMSTATE_MASK = b'\x6f' +SERVER_PURGE_DATA = b'\x70' + +RFC2217_ANSWER_MAP = { + SET_BAUDRATE: SERVER_SET_BAUDRATE, + SET_DATASIZE: SERVER_SET_DATASIZE, + SET_PARITY: SERVER_SET_PARITY, + SET_STOPSIZE: SERVER_SET_STOPSIZE, + SET_CONTROL: SERVER_SET_CONTROL, + NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE, + NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE, + FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND, + FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME, + SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK, + SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK, + PURGE_DATA: SERVER_PURGE_DATA, +} + +SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both) +SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both) +SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both) +SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both) +SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State +SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON +SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF +SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State +SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON +SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF +SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State +SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON +SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF +SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound) +SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound) +SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound) +SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound) +SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both) +SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound) +SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both) + +LINESTATE_MASK_TIMEOUT = 128 # Time-out Error +LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty +LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty +LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error +LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error +LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error +LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error +LINESTATE_MASK_DATA_READY = 1 # Data Ready + +MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect) +MODEMSTATE_MASK_RI = 64 # Ring Indicator +MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State +MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State +MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect +MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector +MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready +MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send + +PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer +PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer +PURGE_BOTH_BUFFERS = b'\x03' # Purge both the access server receive data + # buffer and the access server transmit data buffer + + +RFC2217_PARITY_MAP = { + serial.PARITY_NONE: 1, + serial.PARITY_ODD: 2, + serial.PARITY_EVEN: 3, + serial.PARITY_MARK: 4, + serial.PARITY_SPACE: 5, +} +RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items()) + +RFC2217_STOPBIT_MAP = { + serial.STOPBITS_ONE: 1, + serial.STOPBITS_ONE_POINT_FIVE: 3, + serial.STOPBITS_TWO: 2, +} +RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items()) + +# Telnet filter states +M_NORMAL = 0 +M_IAC_SEEN = 1 +M_NEGOTIATE = 2 + +# TelnetOption and TelnetSubnegotiation states +REQUESTED = 'REQUESTED' +ACTIVE = 'ACTIVE' +INACTIVE = 'INACTIVE' +REALLY_INACTIVE = 'REALLY_INACTIVE' + + +class TelnetOption(object): + """Manage a single telnet option, keeps track of DO/DONT WILL/WONT.""" + + def __init__(self, connection, name, option, send_yes, send_no, ack_yes, + ack_no, initial_state, activation_callback=None): + """\ + Initialize option. + :param connection: connection used to transmit answers + :param name: a readable name for debug outputs + :param send_yes: what to send when option is to be enabled. + :param send_no: what to send when option is to be disabled. + :param ack_yes: what to expect when remote agrees on option. + :param ack_no: what to expect when remote disagrees on option. + :param initial_state: options initialized with REQUESTED are tried to + be enabled on startup. use INACTIVE for all others. + """ + self.connection = connection + self.name = name + self.option = option + self.send_yes = send_yes + self.send_no = send_no + self.ack_yes = ack_yes + self.ack_no = ack_no + self.state = initial_state + self.active = False + self.activation_callback = activation_callback + + def __repr__(self): + """String for debug outputs""" + return "{o.name}:{o.active}({o.state})".format(o=self) + + def process_incoming(self, command): + """\ + A DO/DONT/WILL/WONT was received for this option, update state and + answer when needed. + """ + if command == self.ack_yes: + if self.state is REQUESTED: + self.state = ACTIVE + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is ACTIVE: + pass + elif self.state is INACTIVE: + self.state = ACTIVE + self.connection.telnet_send_option(self.send_yes, self.option) + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is REALLY_INACTIVE: + self.connection.telnet_send_option(self.send_no, self.option) + else: + raise ValueError('option in illegal state {!r}'.format(self)) + elif command == self.ack_no: + if self.state is REQUESTED: + self.state = INACTIVE + self.active = False + elif self.state is ACTIVE: + self.state = INACTIVE + self.connection.telnet_send_option(self.send_no, self.option) + self.active = False + elif self.state is INACTIVE: + pass + elif self.state is REALLY_INACTIVE: + pass + else: + raise ValueError('option in illegal state {!r}'.format(self)) + + +class TelnetSubnegotiation(object): + """\ + A object to handle subnegotiation of options. In this case actually + sub-sub options for RFC 2217. It is used to track com port options. + """ + + def __init__(self, connection, name, option, ack_option=None): + if ack_option is None: + ack_option = option + self.connection = connection + self.name = name + self.option = option + self.value = None + self.ack_option = ack_option + self.state = INACTIVE + + def __repr__(self): + """String for debug outputs.""" + return "{sn.name}:{sn.state}".format(sn=self) + + def set(self, value): + """\ + Request a change of the value. a request is sent to the server. if + the client needs to know if the change is performed he has to check the + state of this object. + """ + self.value = value + self.state = REQUESTED + self.connection.rfc2217_send_subnegotiation(self.option, self.value) + if self.connection.logger: + self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value)) + + def is_ready(self): + """\ + Check if answer from server has been received. when server rejects + the change, raise a ValueError. + """ + if self.state == REALLY_INACTIVE: + raise ValueError("remote rejected value for option {!r}".format(self.name)) + return self.state == ACTIVE + # add property to have a similar interface as TelnetOption + active = property(is_ready) + + def wait(self, timeout=3): + """\ + Wait until the subnegotiation has been acknowledged or timeout. It + can also throw a value error when the answer from the server does not + match the value sent. + """ + timeout_timer = Timeout(timeout) + while not timeout_timer.expired(): + time.sleep(0.05) # prevent 100% CPU load + if self.is_ready(): + break + else: + raise SerialException("timeout while waiting for option {!r}".format(self.name)) + + def check_answer(self, suboption): + """\ + Check an incoming subnegotiation block. The parameter already has + cut off the header like sub option number and com port option value. + """ + if self.value == suboption[:len(self.value)]: + self.state = ACTIVE + else: + # error propagation done in is_ready + self.state = REALLY_INACTIVE + if self.connection.logger: + self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state)) + + +class Serial(SerialBase): + """Serial port implementation for RFC 2217 remote serial ports.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def __init__(self, *args, **kwargs): + self._thread = None + self._socket = None + self._linestate = 0 + self._modemstate = None + self._modemstate_timeout = Timeout(-1) + self._remote_suspend_flow = False + self._write_lock = None + self.logger = None + self._ignore_set_control_answer = False + self._poll_modem_state = False + self._network_timeout = 3 + self._telnet_options = None + self._rfc2217_port_settings = None + self._rfc2217_options = None + self._read_buffer = None + super(Serial, self).__init__(*args, **kwargs) # must be last call in case of auto-open + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + self.logger = None + self._ignore_set_control_answer = False + self._poll_modem_state = False + self._network_timeout = 3 + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + try: + self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5) # XXX good value? + self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception as msg: + self._socket = None + raise SerialException("Could not open port {}: {}".format(self.portstr, msg)) + + # use a thread save queue as buffer. it also simplifies implementing + # the read timeout + self._read_buffer = Queue.Queue() + # to ensure that user writes does not interfere with internal + # telnet/rfc2217 options establish a lock + self._write_lock = threading.Lock() + # name the following separately so that, below, a check can be easily done + mandadory_options = [ + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED), + ] + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED), + ] + mandadory_options + # RFC 2217 specific states + # COM port settings + self._rfc2217_port_settings = { + 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE), + 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE), + 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY), + 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE), + } + # There are more subnegotiation objects, combine all in one dictionary + # for easy access + self._rfc2217_options = { + 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA), + 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL), + } + self._rfc2217_options.update(self._rfc2217_port_settings) + # cache for line and modem states that the server sends to us + self._linestate = 0 + self._modemstate = None + self._modemstate_timeout = Timeout(-1) + # RFC 2217 flow control between server and client + self._remote_suspend_flow = False + + self.is_open = True + self._thread = threading.Thread(target=self._telnet_read_loop) + self._thread.setDaemon(True) + self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port)) + self._thread.start() + + try: # must clean-up if open fails + # negotiate Telnet/RFC 2217 -> send initial requests + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnet_send_option(option.send_yes, option.option) + # now wait until important options are negotiated + timeout = Timeout(self._network_timeout) + while not timeout.expired(): + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options): + break + else: + raise SerialException( + "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options)) + if self.logger: + self.logger.info("Negotiated options: {}".format(self._telnet_options)) + + # fine, go on, set RFC 2217 specific things + self._reconfigure_port() + # all things set up get, now a clean start + if not self._dsrdtr: + self._update_dtr_state() + if not self._rtscts: + self._update_rts_state() + self.reset_input_buffer() + self.reset_output_buffer() + except: + self.close() + raise + + def _reconfigure_port(self): + """Set communication parameters on opened port.""" + if self._socket is None: + raise SerialException("Can only operate on open ports") + + # if self._timeout != 0 and self._interCharTimeout is not None: + # XXX + + if self._write_timeout is not None: + raise NotImplementedError('write_timeout is currently not supported') + # XXX + + # Setup the connection + # to get good performance, all parameter changes are sent first... + if not 0 < self._baudrate < 2 ** 32: + raise ValueError("invalid baudrate: {!r}".format(self._baudrate)) + self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate)) + self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize)) + self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity])) + self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits])) + + # and now wait until parameters are active + items = self._rfc2217_port_settings.values() + if self.logger: + self.logger.debug("Negotiating settings: {}".format(items)) + timeout = Timeout(self._network_timeout) + while not timeout.expired(): + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in items) == len(items): + break + else: + raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items)) + if self.logger: + self.logger.info("Negotiated settings: {}".format(items)) + + if self._rtscts and self._xonxoff: + raise ValueError('xonxoff and rtscts together are not supported') + elif self._rtscts: + self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL) + elif self._xonxoff: + self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL) + else: + self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL) + + def close(self): + """Close port""" + self.is_open = False + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + # ignore errors. + pass + if self._thread: + self._thread.join(7) # XXX more than socket timeout + self._thread = None + # in case of quick reconnects, give the server some time + time.sleep(0.3) + self._socket = None + + def from_url(self, url): + """\ + extract host and port from an URL string, other settings are extracted + an stored in instance + """ + parts = urlparse.urlsplit(url) + if parts.scheme != "rfc2217": + raise SerialException( + 'expected a string in the form ' + '"rfc2217://:[?option[&option...]]": ' + 'not starting with rfc2217:// ({!r})'.format(parts.scheme)) + try: + # process options now, directly altering self + for option, values in urlparse.parse_qs(parts.query, True).items(): + if option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.rfc2217') + self.logger.setLevel(LOGGER_LEVELS[values[0]]) + self.logger.debug('enabled logging') + elif option == 'ign_set_control': + self._ignore_set_control_answer = True + elif option == 'poll_modem': + self._poll_modem_state = True + elif option == 'timeout': + self._network_timeout = float(values[0]) + else: + raise ValueError('unknown option: {!r}'.format(option)) + if not 0 <= parts.port < 65536: + raise ValueError("port not in range 0...65535") + except ValueError as e: + raise SerialException( + 'expected a string in the form ' + '"rfc2217://:[?option[&option...]]": {}'.format(e)) + return (parts.hostname, parts.port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of bytes currently in the input buffer.""" + if not self.is_open: + raise PortNotOpenError() + return self._read_buffer.qsize() + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + data = bytearray() + try: + timeout = Timeout(self._timeout) + while len(data) < size: + if self._thread is None or not self._thread.is_alive(): + raise SerialException('connection failed (reader thread died)') + buf = self._read_buffer.get(True, timeout.time_left()) + if buf is None: + return bytes(data) + data += buf + if timeout.expired(): + break + except Queue.Empty: # -> timeout + pass + return bytes(data) + + def write(self, data): + """\ + Output the given byte string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed. + """ + if not self.is_open: + raise PortNotOpenError() + with self._write_lock: + try: + self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED)) + except socket.error as e: + raise SerialException("connection failed (socket error): {}".format(e)) + return len(data) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER) + # empty read buffer + while self._read_buffer.qsize(): + self._read_buffer.get(False) + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER) + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, to transmitting is + possible. + """ + if not self.is_open: + raise PortNotOpenError() + if self.logger: + self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive')) + if self._break_state: + self.rfc2217_set_control(SET_CONTROL_BREAK_ON) + else: + self.rfc2217_set_control(SET_CONTROL_BREAK_OFF) + + def _update_rts_state(self): + """Set terminal status line: Request To Send.""" + if not self.is_open: + raise PortNotOpenError() + if self.logger: + self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive')) + if self._rts_state: + self.rfc2217_set_control(SET_CONTROL_RTS_ON) + else: + self.rfc2217_set_control(SET_CONTROL_RTS_OFF) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready.""" + if not self.is_open: + raise PortNotOpenError() + if self.logger: + self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive')) + if self._dtr_state: + self.rfc2217_set_control(SET_CONTROL_DTR_ON) + else: + self.rfc2217_set_control(SET_CONTROL_DTR_OFF) + + @property + def cts(self): + """Read terminal status line: Clear To Send.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS) + + @property + def dsr(self): + """Read terminal status line: Data Set Ready.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR) + + @property + def ri(self): + """Read terminal status line: Ring Indicator.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_RI) + + @property + def cd(self): + """Read terminal status line: Carrier Detect.""" + if not self.is_open: + raise PortNotOpenError() + return bool(self.get_modem_state() & MODEMSTATE_MASK_CD) + + # - - - platform specific - - - + # None so far + + # - - - RFC2217 specific - - - + + def _telnet_read_loop(self): + """Read loop for the socket.""" + mode = M_NORMAL + suboption = None + try: + while self.is_open: + try: + data = self._socket.recv(1024) + except socket.timeout: + # just need to get out of recv form time to time to check if + # still alive + continue + except socket.error as e: + # connection fails -> terminate loop + if self.logger: + self.logger.debug("socket error in reader thread: {}".format(e)) + self._read_buffer.put(None) + break + if not data: + self._read_buffer.put(None) + break # lost connection + for byte in iterbytes(data): + if mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + mode = M_IAC_SEEN + else: + # store data in read buffer or sub option buffer + # depending on state + if suboption is not None: + suboption += byte + else: + self._read_buffer.put(byte) + elif mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if suboption is not None: + suboption += IAC + else: + self._read_buffer.put(IAC) + mode = M_NORMAL + elif byte == SB: + # sub option start + suboption = bytearray() + mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnet_process_subnegotiation(bytes(suboption)) + suboption = None + mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + telnet_command = byte + mode = M_NEGOTIATE + else: + # other telnet commands + self._telnet_process_command(byte) + mode = M_NORMAL + elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnet_negotiate_option(telnet_command, byte) + mode = M_NORMAL + finally: + if self.logger: + self.logger.debug("read thread terminated") + + # - incoming telnet commands and options + + def _telnet_process_command(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: {!r}".format(command)) + + def _telnet_negotiate_option(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnet_send_option((DONT if command == WILL else WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: {!r}".format(option)) + + def _telnet_process_subnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3: + self._linestate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate)) + elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3: + self._modemstate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate)) + # update time when we think that a poll would make sense + self._modemstate_timeout.restart(0.3) + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + self._remote_suspend_flow = False + else: + for item in self._rfc2217_options.values(): + if item.ack_option == suboption[1:2]: + #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:]) + item.check_answer(bytes(suboption[2:])) + break + else: + if self.logger: + self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption)) + else: + if self.logger: + self.logger.warning("ignoring subnegotiation: {!r}".format(suboption)) + + # - outgoing telnet commands and options + + def _internal_raw_write(self, data): + """internal socket write with no data escaping. used to send telnet stuff.""" + with self._write_lock: + self._socket.sendall(data) + + def telnet_send_option(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self._internal_raw_write(IAC + action + option) + + def rfc2217_send_subnegotiation(self, option, value=b''): + """Subnegotiation of RFC2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self._internal_raw_write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE) + + def rfc2217_send_purge(self, value): + """\ + Send purge request to the remote. + (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS) + """ + item = self._rfc2217_options['purge'] + item.set(value) # transmit desired purge type + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217_set_control(self, value): + """transmit change of control line to remote""" + item = self._rfc2217_options['control'] + item.set(value) # transmit desired control type + if self._ignore_set_control_answer: + # answers are ignored when option is set. compatibility mode for + # servers that answer, but not the expected one... (or no answer + # at all) i.e. sredird + time.sleep(0.1) # this helps getting the unit tests passed + else: + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217_flow_server_ready(self): + """\ + check if server is ready to receive data. block for some time when + not. + """ + #~ if self._remote_suspend_flow: + #~ wait--- + + def get_modem_state(self): + """\ + get last modem state (cached value. If value is "old", request a new + one. This cache helps that we don't issue to many requests when e.g. all + status lines, one after the other is queried by the user (CTS, DSR + etc.) + """ + # active modem state polling enabled? is the value fresh enough? + if self._poll_modem_state and self._modemstate_timeout.expired(): + if self.logger: + self.logger.debug('polling modem state') + # when it is older, request an update + self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE) + timeout = Timeout(self._network_timeout) + while not timeout.expired(): + time.sleep(0.05) # prevent 100% CPU load + # when expiration time is updated, it means that there is a new + # value + if not self._modemstate_timeout.expired(): + break + else: + if self.logger: + self.logger.warning('poll for modem state failed') + # even when there is a timeout, do not generate an error just + # return the last known value. this way we can support buggy + # servers that do not respond to polls, but send automatic + # updates. + if self._modemstate is not None: + if self.logger: + self.logger.debug('using cached modem state') + return self._modemstate + else: + # never received a notification from the server + raise SerialException("remote sends no NOTIFY_MODEMSTATE") + + +############################################################################# +# The following is code that helps implementing an RFC 2217 server. + +class PortManager(object): + """\ + This class manages the state of Telnet and RFC 2217. It needs a serial + instance and a connection to work with. Connection is expected to implement + a (thread safe) write function, that writes the string to the network. + """ + + def __init__(self, serial_port, connection, logger=None): + self.serial = serial_port + self.connection = connection + self.logger = logger + self._client_is_rfc2217 = False + + # filter state machine + self.mode = M_NORMAL + self.suboption = None + self.telnet_command = None + + # states for modem/line control events + self.modemstate_mask = 255 + self.last_modemstate = None + self.linstate_mask = 0 + + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok), + ] + + # negotiate Telnet/RFC2217 -> send initial requests + if self.logger: + self.logger.debug("requesting initial Telnet/RFC 2217 options") + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnet_send_option(option.send_yes, option.option) + # issue 1st modem state notification + + def _client_ok(self): + """\ + callback of telnet option. It gets called when option is activated. + This one here is used to detect when the client agrees on RFC 2217. A + flag is set so that other functions like check_modem_lines know if the + client is OK. + """ + # The callback is used for we and they so if one party agrees, we're + # already happy. it seems not all servers do the negotiation correctly + # and i guess there are incorrect clients too.. so be happy if client + # answers one or the other positively. + self._client_is_rfc2217 = True + if self.logger: + self.logger.info("client accepts RFC 2217") + # this is to ensure that the client gets a notification, even if there + # was no change + self.check_modem_lines(force_notification=True) + + # - outgoing telnet commands and options + + def telnet_send_option(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self.connection.write(IAC + action + option) + + def rfc2217_send_subnegotiation(self, option, value=b''): + """Subnegotiation of RFC 2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self.connection.write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE) + + # - check modem lines, needs to be called periodically from user to + # establish polling + + def check_modem_lines(self, force_notification=False): + """\ + read control lines from serial port and compare the last value sent to remote. + send updates on changes. + """ + modemstate = ( + (self.serial.cts and MODEMSTATE_MASK_CTS) | + (self.serial.dsr and MODEMSTATE_MASK_DSR) | + (self.serial.ri and MODEMSTATE_MASK_RI) | + (self.serial.cd and MODEMSTATE_MASK_CD)) + # check what has changed + deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0 + if deltas & MODEMSTATE_MASK_CTS: + modemstate |= MODEMSTATE_MASK_CTS_CHANGE + if deltas & MODEMSTATE_MASK_DSR: + modemstate |= MODEMSTATE_MASK_DSR_CHANGE + if deltas & MODEMSTATE_MASK_RI: + modemstate |= MODEMSTATE_MASK_RI_CHANGE + if deltas & MODEMSTATE_MASK_CD: + modemstate |= MODEMSTATE_MASK_CD_CHANGE + # if new state is different and the mask allows this change, send + # notification. suppress notifications when client is not rfc2217 + if modemstate != self.last_modemstate or force_notification: + if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification: + self.rfc2217_send_subnegotiation( + SERVER_NOTIFY_MODEMSTATE, + to_bytes([modemstate & self.modemstate_mask])) + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate)) + # save last state, but forget about deltas. + # otherwise it would also notify about changing deltas which is + # probably not very useful + self.last_modemstate = modemstate & 0xf0 + + # - outgoing data escaping + + def escape(self, data): + """\ + This generator function is for the user. All outgoing data has to be + properly escaped, so that no IAC character in the data stream messes up + the Telnet state machine in the server. + + socket.sendall(escape(data)) + """ + for byte in iterbytes(data): + if byte == IAC: + yield IAC + yield IAC + else: + yield byte + + # - incoming data filter + + def filter(self, data): + """\ + Handle a bunch of incoming bytes. This is a generator. It will yield + all characters not of interest for Telnet/RFC 2217. + + The idea is that the reader thread pushes data from the socket through + this filter: + + for byte in filter(socket.recv(1024)): + # do things like CR/LF conversion/whatever + # and write data to the serial port + serial.write(byte) + + (socket error handling code left as exercise for the reader) + """ + for byte in iterbytes(data): + if self.mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + self.mode = M_IAC_SEEN + else: + # store data in sub option buffer or pass it to our + # consumer depending on state + if self.suboption is not None: + self.suboption += byte + else: + yield byte + elif self.mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if self.suboption is not None: + self.suboption += byte + else: + yield byte + self.mode = M_NORMAL + elif byte == SB: + # sub option start + self.suboption = bytearray() + self.mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnet_process_subnegotiation(bytes(self.suboption)) + self.suboption = None + self.mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + self.telnet_command = byte + self.mode = M_NEGOTIATE + else: + # other telnet commands + self._telnet_process_command(byte) + self.mode = M_NORMAL + elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnet_negotiate_option(self.telnet_command, byte) + self.mode = M_NORMAL + + # - incoming telnet commands and options + + def _telnet_process_command(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: {!r}".format(command)) + + def _telnet_negotiate_option(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnet_send_option((DONT if command == WILL else WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: {!r}".format(option)) + + def _telnet_process_subnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if self.logger: + self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption)) + if suboption[1:2] == SET_BAUDRATE: + backup = self.serial.baudrate + try: + (baudrate,) = struct.unpack(b"!I", suboption[2:6]) + if baudrate != 0: + self.serial.baudrate = baudrate + except ValueError as e: + if self.logger: + self.logger.error("failed to set baud rate: {}".format(e)) + self.serial.baudrate = backup + else: + if self.logger: + self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate)) + self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate)) + elif suboption[1:2] == SET_DATASIZE: + backup = self.serial.bytesize + try: + (datasize,) = struct.unpack(b"!B", suboption[2:3]) + if datasize != 0: + self.serial.bytesize = datasize + except ValueError as e: + if self.logger: + self.logger.error("failed to set data size: {}".format(e)) + self.serial.bytesize = backup + else: + if self.logger: + self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize)) + self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize)) + elif suboption[1:2] == SET_PARITY: + backup = self.serial.parity + try: + parity = struct.unpack(b"!B", suboption[2:3])[0] + if parity != 0: + self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity] + except ValueError as e: + if self.logger: + self.logger.error("failed to set parity: {}".format(e)) + self.serial.parity = backup + else: + if self.logger: + self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity)) + self.rfc2217_send_subnegotiation( + SERVER_SET_PARITY, + struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity])) + elif suboption[1:2] == SET_STOPSIZE: + backup = self.serial.stopbits + try: + stopbits = struct.unpack(b"!B", suboption[2:3])[0] + if stopbits != 0: + self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits] + except ValueError as e: + if self.logger: + self.logger.error("failed to set stop bits: {}".format(e)) + self.serial.stopbits = backup + else: + if self.logger: + self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits)) + self.rfc2217_send_subnegotiation( + SERVER_SET_STOPSIZE, + struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])) + elif suboption[1:2] == SET_CONTROL: + if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING: + if self.serial.xonxoff: + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif self.serial.rtscts: + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + else: + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL: + self.serial.xonxoff = False + self.serial.rtscts = False + if self.logger: + self.logger.info("changed flow control to None") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL: + self.serial.xonxoff = True + if self.logger: + self.logger.info("changed flow control to XON/XOFF") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL: + self.serial.rtscts = True + if self.logger: + self.logger.info("changed flow control to RTS/CTS") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE: + if self.logger: + self.logger.warning("requested break state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_BREAK_ON: + self.serial.break_condition = True + if self.logger: + self.logger.info("changed BREAK to active") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON) + elif suboption[2:3] == SET_CONTROL_BREAK_OFF: + self.serial.break_condition = False + if self.logger: + self.logger.info("changed BREAK to inactive") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_DTR: + if self.logger: + self.logger.warning("requested DTR state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_DTR_ON: + self.serial.dtr = True + if self.logger: + self.logger.info("changed DTR to active") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON) + elif suboption[2:3] == SET_CONTROL_DTR_OFF: + self.serial.dtr = False + if self.logger: + self.logger.info("changed DTR to inactive") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_RTS: + if self.logger: + self.logger.warning("requested RTS state - not implemented") + pass # XXX needs cached value + #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_ON: + self.serial.rts = True + if self.logger: + self.logger.info("changed RTS to active") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_OFF: + self.serial.rts = False + if self.logger: + self.logger.info("changed RTS to inactive") + self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF) + #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL: + elif suboption[1:2] == NOTIFY_LINESTATE: + # client polls for current state + self.rfc2217_send_subnegotiation( + SERVER_NOTIFY_LINESTATE, + to_bytes([0])) # sorry, nothing like that implemented + elif suboption[1:2] == NOTIFY_MODEMSTATE: + if self.logger: + self.logger.info("request for modem state") + # client polls for current state + self.check_modem_lines(force_notification=True) + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + if self.logger: + self.logger.info("suspend") + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + if self.logger: + self.logger.info("resume") + self._remote_suspend_flow = False + elif suboption[1:2] == SET_LINESTATE_MASK: + self.linstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask)) + elif suboption[1:2] == SET_MODEMSTATE_MASK: + self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask)) + elif suboption[1:2] == PURGE_DATA: + if suboption[2:3] == PURGE_RECEIVE_BUFFER: + self.serial.reset_input_buffer() + if self.logger: + self.logger.info("purge in") + self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER) + elif suboption[2:3] == PURGE_TRANSMIT_BUFFER: + self.serial.reset_output_buffer() + if self.logger: + self.logger.info("purge out") + self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER) + elif suboption[2:3] == PURGE_BOTH_BUFFERS: + self.serial.reset_input_buffer() + self.serial.reset_output_buffer() + if self.logger: + self.logger.info("purge both") + self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS) + else: + if self.logger: + self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:]))) + else: + if self.logger: + self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:]))) + else: + if self.logger: + self.logger.warning("unknown subnegotiation: {!r}".format(suboption)) + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('rfc2217://localhost:7000', 115200) + sys.stdout.write('{}\n'.format(s)) + + sys.stdout.write("write...\n") + s.write(b"hello\n") + s.flush() + sys.stdout.write("read: {}\n".format(s.read(5))) + s.close() diff --git a/venv/Lib/site-packages/serial/rs485.py b/venv/Lib/site-packages/serial/rs485.py new file mode 100644 index 0000000..d7aff6f --- /dev/null +++ b/venv/Lib/site-packages/serial/rs485.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# RS485 support +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +"""\ +The settings for RS485 are stored in a dedicated object that can be applied to +serial ports (where supported). +NOTE: Some implementations may only support a subset of the settings. +""" + +from __future__ import absolute_import + +import time +import serial + + +class RS485Settings(object): + def __init__( + self, + rts_level_for_tx=True, + rts_level_for_rx=False, + loopback=False, + delay_before_tx=None, + delay_before_rx=None): + self.rts_level_for_tx = rts_level_for_tx + self.rts_level_for_rx = rts_level_for_rx + self.loopback = loopback + self.delay_before_tx = delay_before_tx + self.delay_before_rx = delay_before_rx + + +class RS485(serial.Serial): + """\ + A subclass that replaces the write method with one that toggles RTS + according to the RS485 settings. + + NOTE: This may work unreliably on some serial ports (control signals not + synchronized or delayed compared to data). Using delays may be + unreliable (varying times, larger than expected) as the OS may not + support very fine grained delays (no smaller than in the order of + tens of milliseconds). + + NOTE: Some implementations support this natively. Better performance + can be expected when the native version is used. + + NOTE: The loopback property is ignored by this implementation. The actual + behavior depends on the used hardware. + + Usage: + + ser = RS485(...) + ser.rs485_mode = RS485Settings(...) + ser.write(b'hello') + """ + + def __init__(self, *args, **kwargs): + super(RS485, self).__init__(*args, **kwargs) + self._alternate_rs485_settings = None + + def write(self, b): + """Write to port, controlling RTS before and after transmitting.""" + if self._alternate_rs485_settings is not None: + # apply level for TX and optional delay + self.setRTS(self._alternate_rs485_settings.rts_level_for_tx) + if self._alternate_rs485_settings.delay_before_tx is not None: + time.sleep(self._alternate_rs485_settings.delay_before_tx) + # write and wait for data to be written + super(RS485, self).write(b) + super(RS485, self).flush() + # optional delay and apply level for RX + if self._alternate_rs485_settings.delay_before_rx is not None: + time.sleep(self._alternate_rs485_settings.delay_before_rx) + self.setRTS(self._alternate_rs485_settings.rts_level_for_rx) + else: + super(RS485, self).write(b) + + # redirect where the property stores the settings so that underlying Serial + # instance does not see them + @property + def rs485_mode(self): + """\ + Enable RS485 mode and apply new settings, set to None to disable. + See serial.rs485.RS485Settings for more info about the value. + """ + return self._alternate_rs485_settings + + @rs485_mode.setter + def rs485_mode(self, rs485_settings): + self._alternate_rs485_settings = rs485_settings diff --git a/venv/Lib/site-packages/serial/serialcli.py b/venv/Lib/site-packages/serial/serialcli.py new file mode 100644 index 0000000..4614736 --- /dev/null +++ b/venv/Lib/site-packages/serial/serialcli.py @@ -0,0 +1,253 @@ +#! python +# +# Backend for .NET/Mono (IronPython), .NET >= 2 +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2008-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import System +import System.IO.Ports +from serial.serialutil import * + +# must invoke function with byte array, make a helper to convert strings +# to byte arrays +sab = System.Array[System.Byte] + + +def as_byte_array(string): + return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython + + +class Serial(SerialBase): + """Serial port implementation for .NET/Mono.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + try: + self._port_handle = System.IO.Ports.SerialPort(self.portstr) + except Exception as msg: + self._port_handle = None + raise SerialException("could not open port %s: %s" % (self.portstr, msg)) + + # if RTS and/or DTR are not set before open, they default to True + if self._rts_state is None: + self._rts_state = True + if self._dtr_state is None: + self._dtr_state = True + + self._reconfigure_port() + self._port_handle.Open() + self.is_open = True + if not self._dsrdtr: + self._update_dtr_state() + if not self._rtscts: + self._update_rts_state() + self.reset_input_buffer() + + def _reconfigure_port(self): + """Set communication parameters on opened port.""" + if not self._port_handle: + raise SerialException("Can only operate on a valid port handle") + + #~ self._port_handle.ReceivedBytesThreshold = 1 + + if self._timeout is None: + self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout + else: + self._port_handle.ReadTimeout = int(self._timeout * 1000) + + # if self._timeout != 0 and self._interCharTimeout is not None: + # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:] + + if self._write_timeout is None: + self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout + else: + self._port_handle.WriteTimeout = int(self._write_timeout * 1000) + + # Setup the connection info. + try: + self._port_handle.BaudRate = self._baudrate + except IOError as e: + # catch errors from illegal baudrate settings + raise ValueError(str(e)) + + if self._bytesize == FIVEBITS: + self._port_handle.DataBits = 5 + elif self._bytesize == SIXBITS: + self._port_handle.DataBits = 6 + elif self._bytesize == SEVENBITS: + self._port_handle.DataBits = 7 + elif self._bytesize == EIGHTBITS: + self._port_handle.DataBits = 8 + else: + raise ValueError("Unsupported number of data bits: %r" % self._bytesize) + + if self._parity == PARITY_NONE: + self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k + elif self._parity == PARITY_EVEN: + self._port_handle.Parity = System.IO.Ports.Parity.Even + elif self._parity == PARITY_ODD: + self._port_handle.Parity = System.IO.Ports.Parity.Odd + elif self._parity == PARITY_MARK: + self._port_handle.Parity = System.IO.Ports.Parity.Mark + elif self._parity == PARITY_SPACE: + self._port_handle.Parity = System.IO.Ports.Parity.Space + else: + raise ValueError("Unsupported parity mode: %r" % self._parity) + + if self._stopbits == STOPBITS_ONE: + self._port_handle.StopBits = System.IO.Ports.StopBits.One + elif self._stopbits == STOPBITS_ONE_POINT_FIVE: + self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive + elif self._stopbits == STOPBITS_TWO: + self._port_handle.StopBits = System.IO.Ports.StopBits.Two + else: + raise ValueError("Unsupported number of stop bits: %r" % self._stopbits) + + if self._rtscts and self._xonxoff: + self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff + elif self._rtscts: + self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend + elif self._xonxoff: + self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff + else: + self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k + + #~ def __del__(self): + #~ self.close() + + def close(self): + """Close port""" + if self.is_open: + if self._port_handle: + try: + self._port_handle.Close() + except System.IO.Ports.InvalidOperationException: + # ignore errors. can happen for unplugged USB serial devices + pass + self._port_handle = None + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of characters currently in the input buffer.""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.BytesToRead + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + # must use single byte reads as this is the only way to read + # without applying encodings + data = bytearray() + while size: + try: + data.append(self._port_handle.ReadByte()) + except System.TimeoutException: + break + else: + size -= 1 + return bytes(data) + + def write(self, data): + """Output the given string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + #~ if not isinstance(data, (bytes, bytearray)): + #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + try: + # must call overloaded method with byte array argument + # as this is the only one not applying encodings + self._port_handle.Write(as_byte_array(data), 0, len(data)) + except System.TimeoutException: + raise SerialTimeoutException('Write timeout') + return len(data) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + self._port_handle.DiscardInBuffer() + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + self._port_handle.DiscardOutBuffer() + + def _update_break_state(self): + """ + Set break: Controls TXD. When active, to transmitting is possible. + """ + if not self.is_open: + raise PortNotOpenError() + self._port_handle.BreakState = bool(self._break_state) + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if not self.is_open: + raise PortNotOpenError() + self._port_handle.RtsEnable = bool(self._rts_state) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if not self.is_open: + raise PortNotOpenError() + self._port_handle.DtrEnable = bool(self._dtr_state) + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.CtsHolding + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.DsrHolding + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + if not self.is_open: + raise PortNotOpenError() + #~ return self._port_handle.XXX + return False # XXX an error would be better + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + if not self.is_open: + raise PortNotOpenError() + return self._port_handle.CDHolding + + # - - platform specific - - - - + # none diff --git a/venv/Lib/site-packages/serial/serialjava.py b/venv/Lib/site-packages/serial/serialjava.py new file mode 100644 index 0000000..0789a78 --- /dev/null +++ b/venv/Lib/site-packages/serial/serialjava.py @@ -0,0 +1,251 @@ +#!jython +# +# Backend Jython with JavaComm +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2002-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +from serial.serialutil import * + + +def my_import(name): + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + +def detect_java_comm(names): + """try given list of modules and return that imports""" + for name in names: + try: + mod = my_import(name) + mod.SerialPort + return mod + except (ImportError, AttributeError): + pass + raise ImportError("No Java Communications API implementation found") + + +# Java Communications API implementations +# http://mho.republika.pl/java/comm/ + +comm = detect_java_comm([ + 'javax.comm', # Sun/IBM + 'gnu.io', # RXTX +]) + + +def device(portnumber): + """Turn a port number into a device name""" + enum = comm.CommPortIdentifier.getPortIdentifiers() + ports = [] + while enum.hasMoreElements(): + el = enum.nextElement() + if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL: + ports.append(el) + return ports[portnumber].getName() + + +class Serial(SerialBase): + """\ + Serial port class, implemented with Java Communications API and + thus usable with jython and the appropriate java extension. + """ + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + if type(self._port) == type(''): # strings are taken directly + portId = comm.CommPortIdentifier.getPortIdentifier(self._port) + else: + portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port)) # numbers are transformed to a comport id obj + try: + self.sPort = portId.open("python serial module", 10) + except Exception as msg: + self.sPort = None + raise SerialException("Could not open port: %s" % msg) + self._reconfigurePort() + self._instream = self.sPort.getInputStream() + self._outstream = self.sPort.getOutputStream() + self.is_open = True + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if not self.sPort: + raise SerialException("Can only operate on a valid port handle") + + self.sPort.enableReceiveTimeout(30) + if self._bytesize == FIVEBITS: + jdatabits = comm.SerialPort.DATABITS_5 + elif self._bytesize == SIXBITS: + jdatabits = comm.SerialPort.DATABITS_6 + elif self._bytesize == SEVENBITS: + jdatabits = comm.SerialPort.DATABITS_7 + elif self._bytesize == EIGHTBITS: + jdatabits = comm.SerialPort.DATABITS_8 + else: + raise ValueError("unsupported bytesize: %r" % self._bytesize) + + if self._stopbits == STOPBITS_ONE: + jstopbits = comm.SerialPort.STOPBITS_1 + elif self._stopbits == STOPBITS_ONE_POINT_FIVE: + jstopbits = comm.SerialPort.STOPBITS_1_5 + elif self._stopbits == STOPBITS_TWO: + jstopbits = comm.SerialPort.STOPBITS_2 + else: + raise ValueError("unsupported number of stopbits: %r" % self._stopbits) + + if self._parity == PARITY_NONE: + jparity = comm.SerialPort.PARITY_NONE + elif self._parity == PARITY_EVEN: + jparity = comm.SerialPort.PARITY_EVEN + elif self._parity == PARITY_ODD: + jparity = comm.SerialPort.PARITY_ODD + elif self._parity == PARITY_MARK: + jparity = comm.SerialPort.PARITY_MARK + elif self._parity == PARITY_SPACE: + jparity = comm.SerialPort.PARITY_SPACE + else: + raise ValueError("unsupported parity type: %r" % self._parity) + + jflowin = jflowout = 0 + if self._rtscts: + jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN + jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT + if self._xonxoff: + jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN + jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT + + self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity) + self.sPort.setFlowControlMode(jflowin | jflowout) + + if self._timeout >= 0: + self.sPort.enableReceiveTimeout(int(self._timeout*1000)) + else: + self.sPort.disableReceiveTimeout() + + def close(self): + """Close port""" + if self.is_open: + if self.sPort: + self._instream.close() + self._outstream.close() + self.sPort.close() + self.sPort = None + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of characters currently in the input buffer.""" + if not self.sPort: + raise PortNotOpenError() + return self._instream.available() + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.sPort: + raise PortNotOpenError() + read = bytearray() + if size > 0: + while len(read) < size: + x = self._instream.read() + if x == -1: + if self.timeout >= 0: + break + else: + read.append(x) + return bytes(read) + + def write(self, data): + """Output the given string over the serial port.""" + if not self.sPort: + raise PortNotOpenError() + if not isinstance(data, (bytes, bytearray)): + raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + self._outstream.write(data) + return len(data) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.sPort: + raise PortNotOpenError() + self._instream.skip(self._instream.available()) + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self.sPort: + raise PortNotOpenError() + self._outstream.flush() + + def send_break(self, duration=0.25): + """Send break condition. Timed, returns to idle state after given duration.""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.sendBreak(duration*1000.0) + + def _update_break_state(self): + """Set break: Controls TXD. When active, to transmitting is possible.""" + if self.fd is None: + raise PortNotOpenError() + raise SerialException("The _update_break_state function is not implemented in java.") + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.setRTS(self._rts_state) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.setDTR(self._dtr_state) + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isCTS() + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isDSR() + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isRI() + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + if not self.sPort: + raise PortNotOpenError() + self.sPort.isCD() diff --git a/venv/Lib/site-packages/serial/serialposix.py b/venv/Lib/site-packages/serial/serialposix.py new file mode 100644 index 0000000..7aceb76 --- /dev/null +++ b/venv/Lib/site-packages/serial/serialposix.py @@ -0,0 +1,900 @@ +#!/usr/bin/env python +# +# backend for serial IO for POSIX compatible systems, like Linux, OSX +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# parts based on code from Grant B. Edwards : +# ftp://ftp.visi.com/users/grante/python/PosixSerial.py +# +# references: http://www.easysw.com/~mike/serial/serial.html + +# Collection of port names (was previously used by number_to_device which was +# removed. +# - Linux /dev/ttyS%d (confirmed) +# - cygwin/win32 /dev/com%d (confirmed) +# - openbsd (OpenBSD) /dev/cua%02d +# - bsd*, freebsd* /dev/cuad%d +# - darwin (OS X) /dev/cuad%d +# - netbsd /dev/dty%02d (NetBSD 1.6 testing by Erk) +# - irix (IRIX) /dev/ttyf%d (partially tested) names depending on flow control +# - hp (HP-UX) /dev/tty%dp0 (not tested) +# - sunos (Solaris/SunOS) /dev/tty%c (letters, 'a'..'z') (confirmed) +# - aix (AIX) /dev/tty%d + + +from __future__ import absolute_import + +# pylint: disable=abstract-method +import errno +import fcntl +import os +import select +import struct +import sys +import termios + +import serial +from serial.serialutil import SerialBase, SerialException, to_bytes, \ + PortNotOpenError, SerialTimeoutException, Timeout + + +class PlatformSpecificBase(object): + BAUDRATE_CONSTANTS = {} + + def _set_special_baudrate(self, baudrate): + raise NotImplementedError('non-standard baudrates are not supported on this platform') + + def _set_rs485_mode(self, rs485_settings): + raise NotImplementedError('RS485 not supported on this platform') + + def set_low_latency_mode(self, low_latency_settings): + raise NotImplementedError('Low latency not supported on this platform') + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self._break_state: + fcntl.ioctl(self.fd, TIOCSBRK) + else: + fcntl.ioctl(self.fd, TIOCCBRK) + + +# some systems support an extra flag to enable the two in POSIX unsupported +# paritiy settings for MARK and SPACE +CMSPAR = 0 # default, for unsupported platforms, override below + +# try to detect the OS so that a device can be selected... +# this code block should supply a device() and set_special_baudrate() function +# for the platform +plat = sys.platform.lower() + +if plat[:5] == 'linux': # Linux (confirmed) # noqa + import array + + # extra termios flags + CMSPAR = 0o10000000000 # Use "stick" (mark/space) parity + + # baudrate ioctls + TCGETS2 = 0x802C542A + TCSETS2 = 0x402C542B + BOTHER = 0o010000 + + # RS485 ioctls + TIOCGRS485 = 0x542E + TIOCSRS485 = 0x542F + SER_RS485_ENABLED = 0b00000001 + SER_RS485_RTS_ON_SEND = 0b00000010 + SER_RS485_RTS_AFTER_SEND = 0b00000100 + SER_RS485_RX_DURING_TX = 0b00010000 + + class PlatformSpecific(PlatformSpecificBase): + BAUDRATE_CONSTANTS = { + 0: 0o000000, # hang up + 50: 0o000001, + 75: 0o000002, + 110: 0o000003, + 134: 0o000004, + 150: 0o000005, + 200: 0o000006, + 300: 0o000007, + 600: 0o000010, + 1200: 0o000011, + 1800: 0o000012, + 2400: 0o000013, + 4800: 0o000014, + 9600: 0o000015, + 19200: 0o000016, + 38400: 0o000017, + 57600: 0o010001, + 115200: 0o010002, + 230400: 0o010003, + 460800: 0o010004, + 500000: 0o010005, + 576000: 0o010006, + 921600: 0o010007, + 1000000: 0o010010, + 1152000: 0o010011, + 1500000: 0o010012, + 2000000: 0o010013, + 2500000: 0o010014, + 3000000: 0o010015, + 3500000: 0o010016, + 4000000: 0o010017 + } + + def set_low_latency_mode(self, low_latency_settings): + buf = array.array('i', [0] * 32) + + try: + # get serial_struct + fcntl.ioctl(self.fd, termios.TIOCGSERIAL, buf) + + # set or unset ASYNC_LOW_LATENCY flag + if low_latency_settings: + buf[4] |= 0x2000 + else: + buf[4] &= ~0x2000 + + # set serial_struct + fcntl.ioctl(self.fd, termios.TIOCSSERIAL, buf) + except IOError as e: + raise ValueError('Failed to update ASYNC_LOW_LATENCY flag to {}: {}'.format(low_latency_settings, e)) + + def _set_special_baudrate(self, baudrate): + # right size is 44 on x86_64, allow for some growth + buf = array.array('i', [0] * 64) + try: + # get serial_struct + fcntl.ioctl(self.fd, TCGETS2, buf) + # set custom speed + buf[2] &= ~termios.CBAUD + buf[2] |= BOTHER + buf[9] = buf[10] = baudrate + + # set serial_struct + fcntl.ioctl(self.fd, TCSETS2, buf) + except IOError as e: + raise ValueError('Failed to set custom baud rate ({}): {}'.format(baudrate, e)) + + def _set_rs485_mode(self, rs485_settings): + buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding + try: + fcntl.ioctl(self.fd, TIOCGRS485, buf) + buf[0] |= SER_RS485_ENABLED + if rs485_settings is not None: + if rs485_settings.loopback: + buf[0] |= SER_RS485_RX_DURING_TX + else: + buf[0] &= ~SER_RS485_RX_DURING_TX + if rs485_settings.rts_level_for_tx: + buf[0] |= SER_RS485_RTS_ON_SEND + else: + buf[0] &= ~SER_RS485_RTS_ON_SEND + if rs485_settings.rts_level_for_rx: + buf[0] |= SER_RS485_RTS_AFTER_SEND + else: + buf[0] &= ~SER_RS485_RTS_AFTER_SEND + if rs485_settings.delay_before_tx is not None: + buf[1] = int(rs485_settings.delay_before_tx * 1000) + if rs485_settings.delay_before_rx is not None: + buf[2] = int(rs485_settings.delay_before_rx * 1000) + else: + buf[0] = 0 # clear SER_RS485_ENABLED + fcntl.ioctl(self.fd, TIOCSRS485, buf) + except IOError as e: + raise ValueError('Failed to set RS485 mode: {}'.format(e)) + + +elif plat == 'cygwin': # cygwin/win32 (confirmed) + + class PlatformSpecific(PlatformSpecificBase): + BAUDRATE_CONSTANTS = { + 128000: 0x01003, + 256000: 0x01005, + 500000: 0x01007, + 576000: 0x01008, + 921600: 0x01009, + 1000000: 0x0100a, + 1152000: 0x0100b, + 1500000: 0x0100c, + 2000000: 0x0100d, + 2500000: 0x0100e, + 3000000: 0x0100f + } + + +elif plat[:6] == 'darwin': # OS X + import array + IOSSIOSPEED = 0x80045402 # _IOW('T', 2, speed_t) + + class PlatformSpecific(PlatformSpecificBase): + osx_version = os.uname()[2].split('.') + TIOCSBRK = 0x2000747B # _IO('t', 123) + TIOCCBRK = 0x2000747A # _IO('t', 122) + + # Tiger or above can support arbitrary serial speeds + if int(osx_version[0]) >= 8: + def _set_special_baudrate(self, baudrate): + # use IOKit-specific call to set up high speeds + buf = array.array('i', [baudrate]) + fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1) + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self._break_state: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK) + else: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK) + +elif plat[:3] == 'bsd' or \ + plat[:7] == 'freebsd' or \ + plat[:6] == 'netbsd' or \ + plat[:7] == 'openbsd': + + class ReturnBaudrate(object): + def __getitem__(self, key): + return key + + class PlatformSpecific(PlatformSpecificBase): + # Only tested on FreeBSD: + # The baud rate may be passed in as + # a literal value. + BAUDRATE_CONSTANTS = ReturnBaudrate() + + TIOCSBRK = 0x2000747B # _IO('t', 123) + TIOCCBRK = 0x2000747A # _IO('t', 122) + + + def _update_break_state(self): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self._break_state: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK) + else: + fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK) + +else: + class PlatformSpecific(PlatformSpecificBase): + pass + + +# load some constants for later use. +# try to use values from termios, use defaults from linux otherwise +TIOCMGET = getattr(termios, 'TIOCMGET', 0x5415) +TIOCMBIS = getattr(termios, 'TIOCMBIS', 0x5416) +TIOCMBIC = getattr(termios, 'TIOCMBIC', 0x5417) +TIOCMSET = getattr(termios, 'TIOCMSET', 0x5418) + +# TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001) +TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002) +TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004) +# TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008) +# TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010) + +TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020) +TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040) +TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080) +TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100) +TIOCM_CD = getattr(termios, 'TIOCM_CD', TIOCM_CAR) +TIOCM_RI = getattr(termios, 'TIOCM_RI', TIOCM_RNG) +# TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000) +# TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000) +if hasattr(termios, 'TIOCINQ'): + TIOCINQ = termios.TIOCINQ +else: + TIOCINQ = getattr(termios, 'FIONREAD', 0x541B) +TIOCOUTQ = getattr(termios, 'TIOCOUTQ', 0x5411) + +TIOCM_zero_str = struct.pack('I', 0) +TIOCM_RTS_str = struct.pack('I', TIOCM_RTS) +TIOCM_DTR_str = struct.pack('I', TIOCM_DTR) + +TIOCSBRK = getattr(termios, 'TIOCSBRK', 0x5427) +TIOCCBRK = getattr(termios, 'TIOCCBRK', 0x5428) + + +class Serial(SerialBase, PlatformSpecific): + """\ + Serial port class POSIX implementation. Serial port configuration is + done with termios and fcntl. Runs on Linux and many other Un*x like + systems. + """ + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened.""" + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + self.fd = None + # open + try: + self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) + except OSError as msg: + self.fd = None + raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) + #~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # set blocking + + self.pipe_abort_read_r, self.pipe_abort_read_w = None, None + self.pipe_abort_write_r, self.pipe_abort_write_w = None, None + + try: + self._reconfigure_port(force_update=True) + + try: + if not self._dsrdtr: + self._update_dtr_state() + if not self._rtscts: + self._update_rts_state() + except IOError as e: + # ignore Invalid argument and Inappropriate ioctl + if e.errno not in (errno.EINVAL, errno.ENOTTY): + raise + + self._reset_input_buffer() + + self.pipe_abort_read_r, self.pipe_abort_read_w = os.pipe() + self.pipe_abort_write_r, self.pipe_abort_write_w = os.pipe() + fcntl.fcntl(self.pipe_abort_read_r, fcntl.F_SETFL, os.O_NONBLOCK) + fcntl.fcntl(self.pipe_abort_write_r, fcntl.F_SETFL, os.O_NONBLOCK) + except BaseException: + try: + os.close(self.fd) + except Exception: + # ignore any exception when closing the port + # also to keep original exception that happened when setting up + pass + self.fd = None + + if self.pipe_abort_read_w is not None: + os.close(self.pipe_abort_read_w) + self.pipe_abort_read_w = None + if self.pipe_abort_read_r is not None: + os.close(self.pipe_abort_read_r) + self.pipe_abort_read_r = None + if self.pipe_abort_write_w is not None: + os.close(self.pipe_abort_write_w) + self.pipe_abort_write_w = None + if self.pipe_abort_write_r is not None: + os.close(self.pipe_abort_write_r) + self.pipe_abort_write_r = None + + raise + + self.is_open = True + + def _reconfigure_port(self, force_update=False): + """Set communication parameters on opened port.""" + if self.fd is None: + raise SerialException("Can only operate on a valid file descriptor") + + # if exclusive lock is requested, create it before we modify anything else + if self._exclusive is not None: + if self._exclusive: + try: + fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError as msg: + raise SerialException(msg.errno, "Could not exclusively lock port {}: {}".format(self._port, msg)) + else: + fcntl.flock(self.fd, fcntl.LOCK_UN) + + custom_baud = None + + vmin = vtime = 0 # timeout is done via select + if self._inter_byte_timeout is not None: + vmin = 1 + vtime = int(self._inter_byte_timeout * 10) + try: + orig_attr = termios.tcgetattr(self.fd) + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr + except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here + raise SerialException("Could not configure port: {}".format(msg)) + # set up raw mode / no echo / binary + cflag |= (termios.CLOCAL | termios.CREAD) + lflag &= ~(termios.ICANON | termios.ECHO | termios.ECHOE | + termios.ECHOK | termios.ECHONL | + termios.ISIG | termios.IEXTEN) # |termios.ECHOPRT + for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk + if hasattr(termios, flag): + lflag &= ~getattr(termios, flag) + + oflag &= ~(termios.OPOST | termios.ONLCR | termios.OCRNL) + iflag &= ~(termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IGNBRK) + if hasattr(termios, 'IUCLC'): + iflag &= ~termios.IUCLC + if hasattr(termios, 'PARMRK'): + iflag &= ~termios.PARMRK + + # setup baud rate + try: + ispeed = ospeed = getattr(termios, 'B{}'.format(self._baudrate)) + except AttributeError: + try: + ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate] + except KeyError: + #~ raise ValueError('Invalid baud rate: %r' % self._baudrate) + + # See if BOTHER is defined for this platform; if it is, use + # this for a speed not defined in the baudrate constants list. + try: + ispeed = ospeed = BOTHER + except NameError: + # may need custom baud rate, it isn't in our list. + ispeed = ospeed = getattr(termios, 'B38400') + + try: + custom_baud = int(self._baudrate) # store for later + except ValueError: + raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate)) + else: + if custom_baud < 0: + raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate)) + + # setup char len + cflag &= ~termios.CSIZE + if self._bytesize == 8: + cflag |= termios.CS8 + elif self._bytesize == 7: + cflag |= termios.CS7 + elif self._bytesize == 6: + cflag |= termios.CS6 + elif self._bytesize == 5: + cflag |= termios.CS5 + else: + raise ValueError('Invalid char len: {!r}'.format(self._bytesize)) + # setup stop bits + if self._stopbits == serial.STOPBITS_ONE: + cflag &= ~(termios.CSTOPB) + elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: + cflag |= (termios.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5 + elif self._stopbits == serial.STOPBITS_TWO: + cflag |= (termios.CSTOPB) + else: + raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits)) + # setup parity + iflag &= ~(termios.INPCK | termios.ISTRIP) + if self._parity == serial.PARITY_NONE: + cflag &= ~(termios.PARENB | termios.PARODD | CMSPAR) + elif self._parity == serial.PARITY_EVEN: + cflag &= ~(termios.PARODD | CMSPAR) + cflag |= (termios.PARENB) + elif self._parity == serial.PARITY_ODD: + cflag &= ~CMSPAR + cflag |= (termios.PARENB | termios.PARODD) + elif self._parity == serial.PARITY_MARK and CMSPAR: + cflag |= (termios.PARENB | CMSPAR | termios.PARODD) + elif self._parity == serial.PARITY_SPACE and CMSPAR: + cflag |= (termios.PARENB | CMSPAR) + cflag &= ~(termios.PARODD) + else: + raise ValueError('Invalid parity: {!r}'.format(self._parity)) + # setup flow control + # xonxoff + if hasattr(termios, 'IXANY'): + if self._xonxoff: + iflag |= (termios.IXON | termios.IXOFF) # |termios.IXANY) + else: + iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY) + else: + if self._xonxoff: + iflag |= (termios.IXON | termios.IXOFF) + else: + iflag &= ~(termios.IXON | termios.IXOFF) + # rtscts + if hasattr(termios, 'CRTSCTS'): + if self._rtscts: + cflag |= (termios.CRTSCTS) + else: + cflag &= ~(termios.CRTSCTS) + elif hasattr(termios, 'CNEW_RTSCTS'): # try it with alternate constant name + if self._rtscts: + cflag |= (termios.CNEW_RTSCTS) + else: + cflag &= ~(termios.CNEW_RTSCTS) + # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails?? + + # buffer + # vmin "minimal number of characters to be read. 0 for non blocking" + if vmin < 0 or vmin > 255: + raise ValueError('Invalid vmin: {!r}'.format(vmin)) + cc[termios.VMIN] = vmin + # vtime + if vtime < 0 or vtime > 255: + raise ValueError('Invalid vtime: {!r}'.format(vtime)) + cc[termios.VTIME] = vtime + # activate settings + if force_update or [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr: + termios.tcsetattr( + self.fd, + termios.TCSANOW, + [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) + + # apply custom baud rate, if any + if custom_baud is not None: + self._set_special_baudrate(custom_baud) + + if self._rs485_mode is not None: + self._set_rs485_mode(self._rs485_mode) + + def close(self): + """Close port""" + if self.is_open: + if self.fd is not None: + os.close(self.fd) + self.fd = None + os.close(self.pipe_abort_read_w) + os.close(self.pipe_abort_read_r) + os.close(self.pipe_abort_write_w) + os.close(self.pipe_abort_write_r) + self.pipe_abort_read_r, self.pipe_abort_read_w = None, None + self.pipe_abort_write_r, self.pipe_abort_write_w = None, None + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of bytes currently in the input buffer.""" + #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) + return struct.unpack('I', s)[0] + + # select based implementation, proved to work on many systems + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + read = bytearray() + timeout = Timeout(self._timeout) + while len(read) < size: + try: + ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left()) + if self.pipe_abort_read_r in ready: + os.read(self.pipe_abort_read_r, 1000) + break + # If select was used with a timeout, and the timeout occurs, it + # returns with empty lists -> thus abort read operation. + # For timeout == 0 (non-blocking operation) also abort when + # there is nothing to read. + if not ready: + break # timeout + buf = os.read(self.fd, size - len(read)) + except OSError as e: + # this is for Python 3.x where select.error is a subclass of + # OSError ignore BlockingIOErrors and EINTR. other errors are shown + # https://www.python.org/dev/peps/pep-0475. + if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('read failed: {}'.format(e)) + except select.error as e: + # this is for Python 2.x + # ignore BlockingIOErrors and EINTR. all errors are shown + # see also http://www.python.org/dev/peps/pep-3151/#select + if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('read failed: {}'.format(e)) + else: + # read should always return some data as select reported it was + # ready to read when we get to this point. + if not buf: + # Disconnected devices, at least on Linux, show the + # behavior that they are always ready to read immediately + # but reading returns nothing. + raise SerialException( + 'device reports readiness to read but returned no data ' + '(device disconnected or multiple access on port?)') + read.extend(buf) + + if timeout.expired(): + break + return bytes(read) + + def cancel_read(self): + if self.is_open: + os.write(self.pipe_abort_read_w, b"x") + + def cancel_write(self): + if self.is_open: + os.write(self.pipe_abort_write_w, b"x") + + def write(self, data): + """Output the given byte string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + d = to_bytes(data) + tx_len = length = len(d) + timeout = Timeout(self._write_timeout) + while tx_len > 0: + try: + n = os.write(self.fd, d) + if timeout.is_non_blocking: + # Zero timeout indicates non-blocking - simply return the + # number of bytes of data actually written + return n + elif not timeout.is_infinite: + # when timeout is set, use select to wait for being ready + # with the time left as timeout + if timeout.expired(): + raise SerialTimeoutException('Write timeout') + abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left()) + if abort: + os.read(self.pipe_abort_write_r, 1000) + break + if not ready: + raise SerialTimeoutException('Write timeout') + else: + assert timeout.time_left() is None + # wait for write operation + abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None) + if abort: + os.read(self.pipe_abort_write_r, 1) + break + if not ready: + raise SerialException('write failed (select)') + d = d[n:] + tx_len -= n + except SerialException: + raise + except OSError as e: + # this is for Python 3.x where select.error is a subclass of + # OSError ignore BlockingIOErrors and EINTR. other errors are shown + # https://www.python.org/dev/peps/pep-0475. + if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('write failed: {}'.format(e)) + except select.error as e: + # this is for Python 2.x + # ignore BlockingIOErrors and EINTR. all errors are shown + # see also http://www.python.org/dev/peps/pep-3151/#select + if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): + raise SerialException('write failed: {}'.format(e)) + if not timeout.is_non_blocking and timeout.expired(): + raise SerialTimeoutException('Write timeout') + return length - len(d) + + def flush(self): + """\ + Flush of file like objects. In this case, wait until all data + is written. + """ + if not self.is_open: + raise PortNotOpenError() + termios.tcdrain(self.fd) + + def _reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + termios.tcflush(self.fd, termios.TCIFLUSH) + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + self._reset_input_buffer() + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and discarding all + that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + termios.tcflush(self.fd, termios.TCOFLUSH) + + def send_break(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self.is_open: + raise PortNotOpenError() + termios.tcsendbreak(self.fd, int(duration / 0.25)) + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if self._rts_state: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if self._dtr_state: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str) + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_CTS != 0 + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_DSR != 0 + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_RI != 0 + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + if not self.is_open: + raise PortNotOpenError() + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I', s)[0] & TIOCM_CD != 0 + + # - - platform specific - - - - + + @property + def out_waiting(self): + """Return the number of bytes currently in the output buffer.""" + #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str) + return struct.unpack('I', s)[0] + + def fileno(self): + """\ + For easier use of the serial port instance with select. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + return self.fd + + def set_input_flow_control(self, enable=True): + """\ + Manually control flow - when software flow control is enabled. + This will send XON (true) or XOFF (false) to the other device. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + if enable: + termios.tcflow(self.fd, termios.TCION) + else: + termios.tcflow(self.fd, termios.TCIOFF) + + def set_output_flow_control(self, enable=True): + """\ + Manually control flow of outgoing data - when hardware or software flow + control is enabled. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + if enable: + termios.tcflow(self.fd, termios.TCOON) + else: + termios.tcflow(self.fd, termios.TCOOFF) + + def nonblocking(self): + """DEPRECATED - has no use""" + import warnings + warnings.warn("nonblocking() has no effect, already nonblocking", DeprecationWarning) + + +class PosixPollSerial(Serial): + """\ + Poll based read implementation. Not all systems support poll properly. + However this one has better handling of errors, such as a device + disconnecting while it's in use (e.g. USB-serial unplugged). + """ + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + read = bytearray() + timeout = Timeout(self._timeout) + poll = select.poll() + poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL) + poll.register(self.pipe_abort_read_r, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL) + if size > 0: + while len(read) < size: + # print "\tread(): size",size, "have", len(read) #debug + # wait until device becomes ready to read (or something fails) + for fd, event in poll.poll(None if timeout.is_infinite else (timeout.time_left() * 1000)): + if fd == self.pipe_abort_read_r: + break + if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL): + raise SerialException('device reports error (poll)') + # we don't care if it is select.POLLIN or timeout, that's + # handled below + if fd == self.pipe_abort_read_r: + os.read(self.pipe_abort_read_r, 1000) + break + buf = os.read(self.fd, size - len(read)) + read.extend(buf) + if timeout.expired() \ + or (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0) and not buf: + break # early abort on timeout + return bytes(read) + + +class VTIMESerial(Serial): + """\ + Implement timeout using vtime of tty device instead of using select. + This means that no inter character timeout can be specified and that + the error handling is degraded. + + Overall timeout is disabled when inter-character timeout is used. + + Note that this implementation does NOT support cancel_read(), it will + just ignore that. + """ + + def _reconfigure_port(self, force_update=True): + """Set communication parameters on opened port.""" + super(VTIMESerial, self)._reconfigure_port() + fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # clear O_NONBLOCK + + if self._inter_byte_timeout is not None: + vmin = 1 + vtime = int(self._inter_byte_timeout * 10) + elif self._timeout is None: + vmin = 1 + vtime = 0 + else: + vmin = 0 + vtime = int(self._timeout * 10) + try: + orig_attr = termios.tcgetattr(self.fd) + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr + except termios.error as msg: # if a port is nonexistent but has a /dev file, it'll fail here + raise serial.SerialException("Could not configure port: {}".format(msg)) + + if vtime < 0 or vtime > 255: + raise ValueError('Invalid vtime: {!r}'.format(vtime)) + cc[termios.VTIME] = vtime + cc[termios.VMIN] = vmin + + termios.tcsetattr( + self.fd, + termios.TCSANOW, + [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + read = bytearray() + while len(read) < size: + buf = os.read(self.fd, size - len(read)) + if not buf: + break + read.extend(buf) + return bytes(read) + + # hack to make hasattr return false + cancel_read = property() diff --git a/venv/Lib/site-packages/serial/serialutil.py b/venv/Lib/site-packages/serial/serialutil.py new file mode 100644 index 0000000..789219e --- /dev/null +++ b/venv/Lib/site-packages/serial/serialutil.py @@ -0,0 +1,697 @@ +#! python +# +# Base class and support functions used by various backends. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import io +import time + +# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)`` +# isn't returning the contents (very unfortunate). Therefore we need special +# cases and test for it. Ensure that there is a ``memoryview`` object for older +# Python versions. This is easier than making every test dependent on its +# existence. +try: + memoryview +except (NameError, AttributeError): + # implementation does not matter as we do not really use it. + # it just must not inherit from something else we might care for. + class memoryview(object): # pylint: disable=redefined-builtin,invalid-name + pass + +try: + unicode +except (NameError, AttributeError): + unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name + +try: + basestring +except (NameError, AttributeError): + basestring = (str,) # for Python 3, pylint: disable=redefined-builtin,invalid-name + + +# "for byte in data" fails for python3 as it returns ints instead of bytes +def iterbytes(b): + """Iterate over bytes, returning bytes instead of ints (python3)""" + if isinstance(b, memoryview): + b = b.tobytes() + i = 0 + while True: + a = b[i:i + 1] + i += 1 + if a: + yield a + else: + break + + +# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11' +# so a simple ``bytes(sequence)`` doesn't work for all versions +def to_bytes(seq): + """convert a sequence to a bytes type""" + if isinstance(seq, bytes): + return seq + elif isinstance(seq, bytearray): + return bytes(seq) + elif isinstance(seq, memoryview): + return seq.tobytes() + elif isinstance(seq, unicode): + raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq)) + else: + # handle list of integers and bytes (one or more items) for Python 2 and 3 + return bytes(bytearray(seq)) + + +# create control bytes +XON = to_bytes([17]) +XOFF = to_bytes([19]) + +CR = to_bytes([13]) +LF = to_bytes([10]) + + +PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S' +STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2) +FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8) + +PARITY_NAMES = { + PARITY_NONE: 'None', + PARITY_EVEN: 'Even', + PARITY_ODD: 'Odd', + PARITY_MARK: 'Mark', + PARITY_SPACE: 'Space', +} + + +class SerialException(IOError): + """Base class for serial port related exceptions.""" + + +class SerialTimeoutException(SerialException): + """Write timeouts give an exception""" + + +class PortNotOpenError(SerialException): + """Port is not open""" + def __init__(self): + super(PortNotOpenError, self).__init__('Attempting to use a port that is not open') + + +class Timeout(object): + """\ + Abstraction for timeout operations. Using time.monotonic() if available + or time.time() in all other cases. + + The class can also be initialized with 0 or None, in order to support + non-blocking and fully blocking I/O operations. The attributes + is_non_blocking and is_infinite are set accordingly. + """ + if hasattr(time, 'monotonic'): + # Timeout implementation with time.monotonic(). This function is only + # supported by Python 3.3 and above. It returns a time in seconds + # (float) just as time.time(), but is not affected by system clock + # adjustments. + TIME = time.monotonic + else: + # Timeout implementation with time.time(). This is compatible with all + # Python versions but has issues if the clock is adjusted while the + # timeout is running. + TIME = time.time + + def __init__(self, duration): + """Initialize a timeout with given duration""" + self.is_infinite = (duration is None) + self.is_non_blocking = (duration == 0) + self.duration = duration + if duration is not None: + self.target_time = self.TIME() + duration + else: + self.target_time = None + + def expired(self): + """Return a boolean, telling if the timeout has expired""" + return self.target_time is not None and self.time_left() <= 0 + + def time_left(self): + """Return how many seconds are left until the timeout expires""" + if self.is_non_blocking: + return 0 + elif self.is_infinite: + return None + else: + delta = self.target_time - self.TIME() + if delta > self.duration: + # clock jumped, recalculate + self.target_time = self.TIME() + self.duration + return self.duration + else: + return max(0, delta) + + def restart(self, duration): + """\ + Restart a timeout, only supported if a timeout was already set up + before. + """ + self.duration = duration + self.target_time = self.TIME() + duration + + +class SerialBase(io.RawIOBase): + """\ + Serial port base class. Provides __init__ function and properties to + get/set port settings. + """ + + # default values, may be overridden in subclasses that do not support all values + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, + 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, + 3000000, 3500000, 4000000) + BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS) + PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE) + STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO) + + def __init__(self, + port=None, + baudrate=9600, + bytesize=EIGHTBITS, + parity=PARITY_NONE, + stopbits=STOPBITS_ONE, + timeout=None, + xonxoff=False, + rtscts=False, + write_timeout=None, + dsrdtr=False, + inter_byte_timeout=None, + exclusive=None, + **kwargs): + """\ + Initialize comm port object. If a "port" is given, then the port will be + opened immediately. Otherwise a Serial port object in closed state + is returned. + """ + + self.is_open = False + self.portstr = None + self.name = None + # correct values are assigned below through properties + self._port = None + self._baudrate = None + self._bytesize = None + self._parity = None + self._stopbits = None + self._timeout = None + self._write_timeout = None + self._xonxoff = None + self._rtscts = None + self._dsrdtr = None + self._inter_byte_timeout = None + self._rs485_mode = None # disabled by default + self._rts_state = True + self._dtr_state = True + self._break_state = False + self._exclusive = None + + # assign values using get/set methods using the properties feature + self.port = port + self.baudrate = baudrate + self.bytesize = bytesize + self.parity = parity + self.stopbits = stopbits + self.timeout = timeout + self.write_timeout = write_timeout + self.xonxoff = xonxoff + self.rtscts = rtscts + self.dsrdtr = dsrdtr + self.inter_byte_timeout = inter_byte_timeout + self.exclusive = exclusive + + # watch for backward compatible kwargs + if 'writeTimeout' in kwargs: + self.write_timeout = kwargs.pop('writeTimeout') + if 'interCharTimeout' in kwargs: + self.inter_byte_timeout = kwargs.pop('interCharTimeout') + if kwargs: + raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs)) + + if port is not None: + self.open() + + # - - - - - - - - - - - - - - - - - - - - - - - - + + # to be implemented by subclasses: + # def open(self): + # def close(self): + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def port(self): + """\ + Get the current port setting. The value that was passed on init or using + setPort() is passed back. + """ + return self._port + + @port.setter + def port(self, port): + """\ + Change the port. + """ + if port is not None and not isinstance(port, basestring): + raise ValueError('"port" must be None or a string, not {}'.format(type(port))) + was_open = self.is_open + if was_open: + self.close() + self.portstr = port + self._port = port + self.name = self.portstr + if was_open: + self.open() + + @property + def baudrate(self): + """Get the current baud rate setting.""" + return self._baudrate + + @baudrate.setter + def baudrate(self, baudrate): + """\ + Change baud rate. It raises a ValueError if the port is open and the + baud rate is not possible. If the port is closed, then the value is + accepted and the exception is raised when the port is opened. + """ + try: + b = int(baudrate) + except TypeError: + raise ValueError("Not a valid baudrate: {!r}".format(baudrate)) + else: + if b < 0: + raise ValueError("Not a valid baudrate: {!r}".format(baudrate)) + self._baudrate = b + if self.is_open: + self._reconfigure_port() + + @property + def bytesize(self): + """Get the current byte size setting.""" + return self._bytesize + + @bytesize.setter + def bytesize(self, bytesize): + """Change byte size.""" + if bytesize not in self.BYTESIZES: + raise ValueError("Not a valid byte size: {!r}".format(bytesize)) + self._bytesize = bytesize + if self.is_open: + self._reconfigure_port() + + @property + def exclusive(self): + """Get the current exclusive access setting.""" + return self._exclusive + + @exclusive.setter + def exclusive(self, exclusive): + """Change the exclusive access setting.""" + self._exclusive = exclusive + if self.is_open: + self._reconfigure_port() + + @property + def parity(self): + """Get the current parity setting.""" + return self._parity + + @parity.setter + def parity(self, parity): + """Change parity setting.""" + if parity not in self.PARITIES: + raise ValueError("Not a valid parity: {!r}".format(parity)) + self._parity = parity + if self.is_open: + self._reconfigure_port() + + @property + def stopbits(self): + """Get the current stop bits setting.""" + return self._stopbits + + @stopbits.setter + def stopbits(self, stopbits): + """Change stop bits size.""" + if stopbits not in self.STOPBITS: + raise ValueError("Not a valid stop bit size: {!r}".format(stopbits)) + self._stopbits = stopbits + if self.is_open: + self._reconfigure_port() + + @property + def timeout(self): + """Get the current timeout setting.""" + return self._timeout + + @timeout.setter + def timeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + try: + timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + if timeout < 0: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + self._timeout = timeout + if self.is_open: + self._reconfigure_port() + + @property + def write_timeout(self): + """Get the current timeout setting.""" + return self._write_timeout + + @write_timeout.setter + def write_timeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + if timeout < 0: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + try: + timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: {!r}".format(timeout)) + + self._write_timeout = timeout + if self.is_open: + self._reconfigure_port() + + @property + def inter_byte_timeout(self): + """Get the current inter-character timeout setting.""" + return self._inter_byte_timeout + + @inter_byte_timeout.setter + def inter_byte_timeout(self, ic_timeout): + """Change inter-byte timeout setting.""" + if ic_timeout is not None: + if ic_timeout < 0: + raise ValueError("Not a valid timeout: {!r}".format(ic_timeout)) + try: + ic_timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: {!r}".format(ic_timeout)) + + self._inter_byte_timeout = ic_timeout + if self.is_open: + self._reconfigure_port() + + @property + def xonxoff(self): + """Get the current XON/XOFF setting.""" + return self._xonxoff + + @xonxoff.setter + def xonxoff(self, xonxoff): + """Change XON/XOFF setting.""" + self._xonxoff = xonxoff + if self.is_open: + self._reconfigure_port() + + @property + def rtscts(self): + """Get the current RTS/CTS flow control setting.""" + return self._rtscts + + @rtscts.setter + def rtscts(self, rtscts): + """Change RTS/CTS flow control setting.""" + self._rtscts = rtscts + if self.is_open: + self._reconfigure_port() + + @property + def dsrdtr(self): + """Get the current DSR/DTR flow control setting.""" + return self._dsrdtr + + @dsrdtr.setter + def dsrdtr(self, dsrdtr=None): + """Change DsrDtr flow control setting.""" + if dsrdtr is None: + # if not set, keep backwards compatibility and follow rtscts setting + self._dsrdtr = self._rtscts + else: + # if defined independently, follow its value + self._dsrdtr = dsrdtr + if self.is_open: + self._reconfigure_port() + + @property + def rts(self): + return self._rts_state + + @rts.setter + def rts(self, value): + self._rts_state = value + if self.is_open: + self._update_rts_state() + + @property + def dtr(self): + return self._dtr_state + + @dtr.setter + def dtr(self, value): + self._dtr_state = value + if self.is_open: + self._update_dtr_state() + + @property + def break_condition(self): + return self._break_state + + @break_condition.setter + def break_condition(self, value): + self._break_state = value + if self.is_open: + self._update_break_state() + + # - - - - - - - - - - - - - - - - - - - - - - - - + # functions useful for RS-485 adapters + + @property + def rs485_mode(self): + """\ + Enable RS485 mode and apply new settings, set to None to disable. + See serial.rs485.RS485Settings for more info about the value. + """ + return self._rs485_mode + + @rs485_mode.setter + def rs485_mode(self, rs485_settings): + self._rs485_mode = rs485_settings + if self.is_open: + self._reconfigure_port() + + # - - - - - - - - - - - - - - - - - - - - - - - - + + _SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff', + 'dsrdtr', 'rtscts', 'timeout', 'write_timeout', + 'inter_byte_timeout') + + def get_settings(self): + """\ + Get current port settings as a dictionary. For use with + apply_settings(). + """ + return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS]) + + def apply_settings(self, d): + """\ + Apply stored settings from a dictionary returned from + get_settings(). It's allowed to delete keys from the dictionary. These + values will simply left unchanged. + """ + for key in self._SAVED_SETTINGS: + if key in d and d[key] != getattr(self, '_' + key): # check against internal "_" value + setattr(self, key, d[key]) # set non "_" value to use properties write function + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def __repr__(self): + """String representation of the current port settings and its state.""" + return '{name}(port={p.portstr!r}, ' \ + 'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \ + 'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \ + 'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format( + name=self.__class__.__name__, id=id(self), p=self) + + # - - - - - - - - - - - - - - - - - - - - - - - - + # compatibility with io library + # pylint: disable=invalid-name,missing-docstring + + def readable(self): + return True + + def writable(self): + return True + + def seekable(self): + return False + + def readinto(self, b): + data = self.read(len(b)) + n = len(data) + try: + b[:n] = data + except TypeError as err: + import array + if not isinstance(b, array.array): + raise err + b[:n] = array.array('b', data) + return n + + # - - - - - - - - - - - - - - - - - - - - - - - - + # context manager + + def __enter__(self): + if self._port is not None and not self.is_open: + self.open() + return self + + def __exit__(self, *args, **kwargs): + self.close() + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def send_break(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self.is_open: + raise PortNotOpenError() + self.break_condition = True + time.sleep(duration) + self.break_condition = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + # backwards compatibility / deprecated functions + + def flushInput(self): + self.reset_input_buffer() + + def flushOutput(self): + self.reset_output_buffer() + + def inWaiting(self): + return self.in_waiting + + def sendBreak(self, duration=0.25): + self.send_break(duration) + + def setRTS(self, value=1): + self.rts = value + + def setDTR(self, value=1): + self.dtr = value + + def getCTS(self): + return self.cts + + def getDSR(self): + return self.dsr + + def getRI(self): + return self.ri + + def getCD(self): + return self.cd + + def setPort(self, port): + self.port = port + + @property + def writeTimeout(self): + return self.write_timeout + + @writeTimeout.setter + def writeTimeout(self, timeout): + self.write_timeout = timeout + + @property + def interCharTimeout(self): + return self.inter_byte_timeout + + @interCharTimeout.setter + def interCharTimeout(self, interCharTimeout): + self.inter_byte_timeout = interCharTimeout + + def getSettingsDict(self): + return self.get_settings() + + def applySettingsDict(self, d): + self.apply_settings(d) + + def isOpen(self): + return self.is_open + + # - - - - - - - - - - - - - - - - - - - - - - - - + # additional functionality + + def read_all(self): + """\ + Read all bytes currently available in the buffer of the OS. + """ + return self.read(self.in_waiting) + + def read_until(self, expected=LF, size=None): + """\ + Read until an expected sequence is found ('\n' by default), the size + is exceeded or until timeout occurs. + """ + lenterm = len(expected) + line = bytearray() + timeout = Timeout(self._timeout) + while True: + c = self.read(1) + if c: + line += c + if line[-lenterm:] == expected: + break + if size is not None and len(line) >= size: + break + else: + break + if timeout.expired(): + break + return bytes(line) + + def iread_until(self, *args, **kwargs): + """\ + Read lines, implemented as generator. It will raise StopIteration on + timeout (empty read). + """ + while True: + line = self.read_until(*args, **kwargs) + if not line: + break + yield line + + +# - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + import sys + s = SerialBase() + sys.stdout.write('port name: {}\n'.format(s.name)) + sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES)) + sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES)) + sys.stdout.write('parities: {}\n'.format(s.PARITIES)) + sys.stdout.write('stop bits: {}\n'.format(s.STOPBITS)) + sys.stdout.write('{}\n'.format(s)) diff --git a/venv/Lib/site-packages/serial/serialwin32.py b/venv/Lib/site-packages/serial/serialwin32.py new file mode 100644 index 0000000..e7da929 --- /dev/null +++ b/venv/Lib/site-packages/serial/serialwin32.py @@ -0,0 +1,477 @@ +#! python +# +# backend for Windows ("win32" incl. 32/64 bit support) +# +# (C) 2001-2020 Chris Liechti +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# SPDX-License-Identifier: BSD-3-Clause +# +# Initial patch to use ctypes by Giovanni Bajo + +from __future__ import absolute_import + +# pylint: disable=invalid-name,too-few-public-methods +import ctypes +import time +from serial import win32 + +import serial +from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException + + +class Serial(SerialBase): + """Serial port implementation for Win32 based on ctypes.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def __init__(self, *args, **kwargs): + self._port_handle = None + self._overlapped_read = None + self._overlapped_write = None + super(Serial, self).__init__(*args, **kwargs) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + # the "\\.\COMx" format is required for devices other than COM1-COM8 + # not all versions of windows seem to support this properly + # so that the first few ports are used with the DOS device name + port = self.name + try: + if port.upper().startswith('COM') and int(port[3:]) > 8: + port = '\\\\.\\' + port + except ValueError: + # for like COMnotanumber + pass + self._port_handle = win32.CreateFile( + port, + win32.GENERIC_READ | win32.GENERIC_WRITE, + 0, # exclusive access + None, # no security + win32.OPEN_EXISTING, + win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED, + 0) + if self._port_handle == win32.INVALID_HANDLE_VALUE: + self._port_handle = None # 'cause __del__ is called anyway + raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError())) + + try: + self._overlapped_read = win32.OVERLAPPED() + self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None) + self._overlapped_write = win32.OVERLAPPED() + #~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None) + self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None) + + # Setup a 4k buffer + win32.SetupComm(self._port_handle, 4096, 4096) + + # Save original timeout values: + self._orgTimeouts = win32.COMMTIMEOUTS() + win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts)) + + self._reconfigure_port() + + # Clear buffers: + # Remove anything that was there + win32.PurgeComm( + self._port_handle, + win32.PURGE_TXCLEAR | win32.PURGE_TXABORT | + win32.PURGE_RXCLEAR | win32.PURGE_RXABORT) + except: + try: + self._close() + except: + # ignore any exception when closing the port + # also to keep original exception that happened when setting up + pass + self._port_handle = None + raise + else: + self.is_open = True + + def _reconfigure_port(self): + """Set communication parameters on opened port.""" + if not self._port_handle: + raise SerialException("Can only operate on a valid port handle") + + # Set Windows timeout values + # timeouts is a tuple with the following items: + # (ReadIntervalTimeout,ReadTotalTimeoutMultiplier, + # ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier, + # WriteTotalTimeoutConstant) + timeouts = win32.COMMTIMEOUTS() + if self._timeout is None: + pass # default of all zeros is OK + elif self._timeout == 0: + timeouts.ReadIntervalTimeout = win32.MAXDWORD + else: + timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1) + if self._timeout != 0 and self._inter_byte_timeout is not None: + timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1) + + if self._write_timeout is None: + pass + elif self._write_timeout == 0: + timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD + else: + timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1) + win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts)) + + win32.SetCommMask(self._port_handle, win32.EV_ERR) + + # Setup the connection info. + # Get state and modify it: + comDCB = win32.DCB() + win32.GetCommState(self._port_handle, ctypes.byref(comDCB)) + comDCB.BaudRate = self._baudrate + + if self._bytesize == serial.FIVEBITS: + comDCB.ByteSize = 5 + elif self._bytesize == serial.SIXBITS: + comDCB.ByteSize = 6 + elif self._bytesize == serial.SEVENBITS: + comDCB.ByteSize = 7 + elif self._bytesize == serial.EIGHTBITS: + comDCB.ByteSize = 8 + else: + raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize)) + + if self._parity == serial.PARITY_NONE: + comDCB.Parity = win32.NOPARITY + comDCB.fParity = 0 # Disable Parity Check + elif self._parity == serial.PARITY_EVEN: + comDCB.Parity = win32.EVENPARITY + comDCB.fParity = 1 # Enable Parity Check + elif self._parity == serial.PARITY_ODD: + comDCB.Parity = win32.ODDPARITY + comDCB.fParity = 1 # Enable Parity Check + elif self._parity == serial.PARITY_MARK: + comDCB.Parity = win32.MARKPARITY + comDCB.fParity = 1 # Enable Parity Check + elif self._parity == serial.PARITY_SPACE: + comDCB.Parity = win32.SPACEPARITY + comDCB.fParity = 1 # Enable Parity Check + else: + raise ValueError("Unsupported parity mode: {!r}".format(self._parity)) + + if self._stopbits == serial.STOPBITS_ONE: + comDCB.StopBits = win32.ONESTOPBIT + elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: + comDCB.StopBits = win32.ONE5STOPBITS + elif self._stopbits == serial.STOPBITS_TWO: + comDCB.StopBits = win32.TWOSTOPBITS + else: + raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits)) + + comDCB.fBinary = 1 # Enable Binary Transmission + # Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE) + if self._rs485_mode is None: + if self._rtscts: + comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE + else: + comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE + comDCB.fOutxCtsFlow = self._rtscts + else: + # checks for unsupported settings + # XXX verify if platform really does not have a setting for those + if not self._rs485_mode.rts_level_for_tx: + raise ValueError( + 'Unsupported value for RS485Settings.rts_level_for_tx: {!r} (only True is allowed)'.format( + self._rs485_mode.rts_level_for_tx,)) + if self._rs485_mode.rts_level_for_rx: + raise ValueError( + 'Unsupported value for RS485Settings.rts_level_for_rx: {!r} (only False is allowed)'.format( + self._rs485_mode.rts_level_for_rx,)) + if self._rs485_mode.delay_before_tx is not None: + raise ValueError( + 'Unsupported value for RS485Settings.delay_before_tx: {!r} (only None is allowed)'.format( + self._rs485_mode.delay_before_tx,)) + if self._rs485_mode.delay_before_rx is not None: + raise ValueError( + 'Unsupported value for RS485Settings.delay_before_rx: {!r} (only None is allowed)'.format( + self._rs485_mode.delay_before_rx,)) + if self._rs485_mode.loopback: + raise ValueError( + 'Unsupported value for RS485Settings.loopback: {!r} (only False is allowed)'.format( + self._rs485_mode.loopback,)) + comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE + comDCB.fOutxCtsFlow = 0 + + if self._dsrdtr: + comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE + else: + comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE + comDCB.fOutxDsrFlow = self._dsrdtr + comDCB.fOutX = self._xonxoff + comDCB.fInX = self._xonxoff + comDCB.fNull = 0 + comDCB.fErrorChar = 0 + comDCB.fAbortOnError = 0 + comDCB.XonChar = serial.XON + comDCB.XoffChar = serial.XOFF + + if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)): + raise SerialException( + 'Cannot configure port, something went wrong. ' + 'Original message: {!r}'.format(ctypes.WinError())) + + #~ def __del__(self): + #~ self.close() + + def _close(self): + """internal close port helper""" + if self._port_handle is not None: + # Restore original timeout values: + win32.SetCommTimeouts(self._port_handle, self._orgTimeouts) + if self._overlapped_read is not None: + self.cancel_read() + win32.CloseHandle(self._overlapped_read.hEvent) + self._overlapped_read = None + if self._overlapped_write is not None: + self.cancel_write() + win32.CloseHandle(self._overlapped_write.hEvent) + self._overlapped_write = None + win32.CloseHandle(self._port_handle) + self._port_handle = None + + def close(self): + """Close port""" + if self.is_open: + self._close() + self.is_open = False + + # - - - - - - - - - - - - - - - - - - - - - - - - + + @property + def in_waiting(self): + """Return the number of bytes currently in the input buffer.""" + flags = win32.DWORD() + comstat = win32.COMSTAT() + if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): + raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) + return comstat.cbInQue + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self.is_open: + raise PortNotOpenError() + if size > 0: + win32.ResetEvent(self._overlapped_read.hEvent) + flags = win32.DWORD() + comstat = win32.COMSTAT() + if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): + raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) + n = min(comstat.cbInQue, size) if self.timeout == 0 else size + if n > 0: + buf = ctypes.create_string_buffer(n) + rc = win32.DWORD() + read_ok = win32.ReadFile( + self._port_handle, + buf, + n, + ctypes.byref(rc), + ctypes.byref(self._overlapped_read)) + if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError())) + result_ok = win32.GetOverlappedResult( + self._port_handle, + ctypes.byref(self._overlapped_read), + ctypes.byref(rc), + True) + if not result_ok: + if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED: + raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError())) + read = buf.raw[:rc.value] + else: + read = bytes() + else: + read = bytes() + return bytes(read) + + def write(self, data): + """Output the given byte string over the serial port.""" + if not self.is_open: + raise PortNotOpenError() + #~ if not isinstance(data, (bytes, bytearray)): + #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + # convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview + data = to_bytes(data) + if data: + #~ win32event.ResetEvent(self._overlapped_write.hEvent) + n = win32.DWORD() + success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write) + if self._write_timeout != 0: # if blocking (None) or w/ write timeout (>0) + if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) + + # Wait for the write to complete. + #~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE) + win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True) + if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED: + return n.value # canceled IO is no error + if n.value != len(data): + raise SerialTimeoutException('Write timeout') + return n.value + else: + errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError() + if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_OPERATION_ABORTED): + return 0 + elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING): + # no info on true length provided by OS function in async mode + return len(data) + else: + raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError())) + else: + return 0 + + def flush(self): + """\ + Flush of file like objects. In this case, wait until all data + is written. + """ + while self.out_waiting: + time.sleep(0.05) + # XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would + # require overlapped IO and it's also only possible to set a single mask + # on the port--- + + def reset_input_buffer(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self.is_open: + raise PortNotOpenError() + win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT) + + def reset_output_buffer(self): + """\ + Clear output buffer, aborting the current output and discarding all + that is in the buffer. + """ + if not self.is_open: + raise PortNotOpenError() + win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT) + + def _update_break_state(self): + """Set break: Controls TXD. When active, to transmitting is possible.""" + if not self.is_open: + raise PortNotOpenError() + if self._break_state: + win32.SetCommBreak(self._port_handle) + else: + win32.ClearCommBreak(self._port_handle) + + def _update_rts_state(self): + """Set terminal status line: Request To Send""" + if self._rts_state: + win32.EscapeCommFunction(self._port_handle, win32.SETRTS) + else: + win32.EscapeCommFunction(self._port_handle, win32.CLRRTS) + + def _update_dtr_state(self): + """Set terminal status line: Data Terminal Ready""" + if self._dtr_state: + win32.EscapeCommFunction(self._port_handle, win32.SETDTR) + else: + win32.EscapeCommFunction(self._port_handle, win32.CLRDTR) + + def _GetCommModemStatus(self): + if not self.is_open: + raise PortNotOpenError() + stat = win32.DWORD() + win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat)) + return stat.value + + @property + def cts(self): + """Read terminal status line: Clear To Send""" + return win32.MS_CTS_ON & self._GetCommModemStatus() != 0 + + @property + def dsr(self): + """Read terminal status line: Data Set Ready""" + return win32.MS_DSR_ON & self._GetCommModemStatus() != 0 + + @property + def ri(self): + """Read terminal status line: Ring Indicator""" + return win32.MS_RING_ON & self._GetCommModemStatus() != 0 + + @property + def cd(self): + """Read terminal status line: Carrier Detect""" + return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0 + + # - - platform specific - - - - + + def set_buffer_size(self, rx_size=4096, tx_size=None): + """\ + Recommend a buffer size to the driver (device driver can ignore this + value). Must be called after the port is opened. + """ + if tx_size is None: + tx_size = rx_size + win32.SetupComm(self._port_handle, rx_size, tx_size) + + def set_output_flow_control(self, enable=True): + """\ + Manually control flow - when software flow control is enabled. + This will do the same as if XON (true) or XOFF (false) are received + from the other device and control the transmission accordingly. + WARNING: this function is not portable to different platforms! + """ + if not self.is_open: + raise PortNotOpenError() + if enable: + win32.EscapeCommFunction(self._port_handle, win32.SETXON) + else: + win32.EscapeCommFunction(self._port_handle, win32.SETXOFF) + + @property + def out_waiting(self): + """Return how many bytes the in the outgoing buffer""" + flags = win32.DWORD() + comstat = win32.COMSTAT() + if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)): + raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError())) + return comstat.cbOutQue + + def _cancel_overlapped_io(self, overlapped): + """Cancel a blocking read operation, may be called from other thread""" + # check if read operation is pending + rc = win32.DWORD() + err = win32.GetOverlappedResult( + self._port_handle, + ctypes.byref(overlapped), + ctypes.byref(rc), + False) + if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE): + # cancel, ignoring any errors (e.g. it may just have finished on its own) + win32.CancelIoEx(self._port_handle, overlapped) + + def cancel_read(self): + """Cancel a blocking read operation, may be called from other thread""" + self._cancel_overlapped_io(self._overlapped_read) + + def cancel_write(self): + """Cancel a blocking write operation, may be called from other thread""" + self._cancel_overlapped_io(self._overlapped_write) + + @SerialBase.exclusive.setter + def exclusive(self, exclusive): + """Change the exclusive access setting.""" + if exclusive is not None and not exclusive: + raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive)) + else: + serial.SerialBase.exclusive.__set__(self, exclusive) diff --git a/venv/Lib/site-packages/serial/threaded/__init__.py b/venv/Lib/site-packages/serial/threaded/__init__.py new file mode 100644 index 0000000..b8940b6 --- /dev/null +++ b/venv/Lib/site-packages/serial/threaded/__init__.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +# +# Working with threading and pySerial +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015-2016 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +"""\ +Support threading with serial ports. +""" +from __future__ import absolute_import + +import serial +import threading + + +class Protocol(object): + """\ + Protocol as used by the ReaderThread. This base class provides empty + implementations of all methods. + """ + + def connection_made(self, transport): + """Called when reader thread is started""" + + def data_received(self, data): + """Called with snippets received from the serial port""" + + def connection_lost(self, exc): + """\ + Called when the serial port is closed or the reader loop terminated + otherwise. + """ + if isinstance(exc, Exception): + raise exc + + +class Packetizer(Protocol): + """ + Read binary packets from serial port. Packets are expected to be terminated + with a TERMINATOR byte (null byte by default). + + The class also keeps track of the transport. + """ + + TERMINATOR = b'\0' + + def __init__(self): + self.buffer = bytearray() + self.transport = None + + def connection_made(self, transport): + """Store transport""" + self.transport = transport + + def connection_lost(self, exc): + """Forget transport""" + self.transport = None + super(Packetizer, self).connection_lost(exc) + + def data_received(self, data): + """Buffer received data, find TERMINATOR, call handle_packet""" + self.buffer.extend(data) + while self.TERMINATOR in self.buffer: + packet, self.buffer = self.buffer.split(self.TERMINATOR, 1) + self.handle_packet(packet) + + def handle_packet(self, packet): + """Process packets - to be overridden by subclassing""" + raise NotImplementedError('please implement functionality in handle_packet') + + +class FramedPacket(Protocol): + """ + Read binary packets. Packets are expected to have a start and stop marker. + + The class also keeps track of the transport. + """ + + START = b'(' + STOP = b')' + + def __init__(self): + self.packet = bytearray() + self.in_packet = False + self.transport = None + + def connection_made(self, transport): + """Store transport""" + self.transport = transport + + def connection_lost(self, exc): + """Forget transport""" + self.transport = None + self.in_packet = False + del self.packet[:] + super(FramedPacket, self).connection_lost(exc) + + def data_received(self, data): + """Find data enclosed in START/STOP, call handle_packet""" + for byte in serial.iterbytes(data): + if byte == self.START: + self.in_packet = True + elif byte == self.STOP: + self.in_packet = False + self.handle_packet(bytes(self.packet)) # make read-only copy + del self.packet[:] + elif self.in_packet: + self.packet.extend(byte) + else: + self.handle_out_of_packet_data(byte) + + def handle_packet(self, packet): + """Process packets - to be overridden by subclassing""" + raise NotImplementedError('please implement functionality in handle_packet') + + def handle_out_of_packet_data(self, data): + """Process data that is received outside of packets""" + pass + + +class LineReader(Packetizer): + """ + Read and write (Unicode) lines from/to serial port. + The encoding is applied. + """ + + TERMINATOR = b'\r\n' + ENCODING = 'utf-8' + UNICODE_HANDLING = 'replace' + + def handle_packet(self, packet): + self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING)) + + def handle_line(self, line): + """Process one line - to be overridden by subclassing""" + raise NotImplementedError('please implement functionality in handle_line') + + def write_line(self, text): + """ + Write text to the transport. ``text`` is a Unicode string and the encoding + is applied before sending ans also the newline is append. + """ + # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call + self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR) + + +class ReaderThread(threading.Thread): + """\ + Implement a serial port read loop and dispatch to a Protocol instance (like + the asyncio.Protocol) but do it with threads. + + Calls to close() will close the serial port but it is also possible to just + stop() this thread and continue the serial port instance otherwise. + """ + + def __init__(self, serial_instance, protocol_factory): + """\ + Initialize thread. + + Note that the serial_instance' timeout is set to one second! + Other settings are not changed. + """ + super(ReaderThread, self).__init__() + self.daemon = True + self.serial = serial_instance + self.protocol_factory = protocol_factory + self.alive = True + self._lock = threading.Lock() + self._connection_made = threading.Event() + self.protocol = None + + def stop(self): + """Stop the reader thread""" + self.alive = False + if hasattr(self.serial, 'cancel_read'): + self.serial.cancel_read() + self.join(2) + + def run(self): + """Reader loop""" + if not hasattr(self.serial, 'cancel_read'): + self.serial.timeout = 1 + self.protocol = self.protocol_factory() + try: + self.protocol.connection_made(self) + except Exception as e: + self.alive = False + self.protocol.connection_lost(e) + self._connection_made.set() + return + error = None + self._connection_made.set() + while self.alive and self.serial.is_open: + try: + # read all that is there or wait for one byte (blocking) + data = self.serial.read(self.serial.in_waiting or 1) + except serial.SerialException as e: + # probably some I/O problem such as disconnected USB serial + # adapters -> exit + error = e + break + else: + if data: + # make a separated try-except for called user code + try: + self.protocol.data_received(data) + except Exception as e: + error = e + break + self.alive = False + self.protocol.connection_lost(error) + self.protocol = None + + def write(self, data): + """Thread safe writing (uses lock)""" + with self._lock: + return self.serial.write(data) + + def close(self): + """Close the serial port and exit reader thread (uses lock)""" + # use the lock to let other threads finish writing + with self._lock: + # first stop reading, so that closing can be done on idle port + self.stop() + self.serial.close() + + def connect(self): + """ + Wait until connection is set up and return the transport and protocol + instances. + """ + if self.alive: + self._connection_made.wait() + if not self.alive: + raise RuntimeError('connection_lost already called') + return (self, self.protocol) + else: + raise RuntimeError('already stopped') + + # - - context manager, returns protocol + + def __enter__(self): + """\ + Enter context handler. May raise RuntimeError in case the connection + could not be created. + """ + self.start() + self._connection_made.wait() + if not self.alive: + raise RuntimeError('connection_lost already called') + return self.protocol + + def __exit__(self, exc_type, exc_val, exc_tb): + """Leave context: close port""" + self.close() + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + # pylint: disable=wrong-import-position + import sys + import time + import traceback + + #~ PORT = 'spy:///dev/ttyUSB0' + PORT = 'loop://' + + class PrintLines(LineReader): + def connection_made(self, transport): + super(PrintLines, self).connection_made(transport) + sys.stdout.write('port opened\n') + self.write_line('hello world') + + def handle_line(self, data): + sys.stdout.write('line received: {!r}\n'.format(data)) + + def connection_lost(self, exc): + if exc: + traceback.print_exc(exc) + sys.stdout.write('port closed\n') + + ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) + with ReaderThread(ser, PrintLines) as protocol: + protocol.write_line('hello') + time.sleep(2) + + # alternative usage + ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) + t = ReaderThread(ser, PrintLines) + t.start() + transport, protocol = t.connect() + protocol.write_line('hello') + time.sleep(2) + t.close() diff --git a/venv/Lib/site-packages/serial/threaded/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/serial/threaded/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1687db91943537ea6793b01903152b6c84daa78 GIT binary patch literal 9544 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 NFS`wtU=_-`{l7Y}oRI(k literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/tools/__init__.py b/venv/Lib/site-packages/serial/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..381cd29526e5d0113b8a3ff20f221adfb5843943 GIT binary patch literal 180 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 RV$wpSEpIc3ThWIR`4yTG>dpWF literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_common.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_common.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c93401ad1452c868bc598c2b3f6c9f3bf6ae77f2 GIT binary patch literal 3564 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_posix.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_posix.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f0c06647191d9e5d0ef4d8c003b60b413e2e939 GIT binary patch literal 3929 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 adcYGK?V|OWw_zIb$M#=D?!+zfasLN{kew(1 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/miniterm.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/miniterm.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b1977c7b053de8c6bc9aa6b655342f6534bb702 GIT binary patch literal 29322 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 +# +# SPDX-License-Identifier: BSD-3-Clause +"""\ +Python 'hex' Codec - 2-digit hex with spaces content transfer encoding. + +Encode and decode may be a bit missleading at first sight... + +The textual representation is a hex dump: e.g. "40 41" +The "encoded" data of this is the binary form, e.g. b"@A" + +Therefore decoding is binary to text and thus converting binary data to hex dump. + +""" + +from __future__ import absolute_import + +import codecs +import serial + + +try: + unicode +except (NameError, AttributeError): + unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name + + +HEXDIGITS = '0123456789ABCDEF' + + +# Codec APIs + +def hex_encode(data, errors='strict'): + """'40 41 42' -> b'@ab'""" + return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data)) + + +def hex_decode(data, errors='strict'): + """b'@ab' -> '40 41 42'""" + return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data)) + + +class Codec(codecs.Codec): + def encode(self, data, errors='strict'): + """'40 41 42' -> b'@ab'""" + return serial.to_bytes([int(h, 16) for h in data.split()]) + + def decode(self, data, errors='strict'): + """b'@ab' -> '40 41 42'""" + return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) + + +class IncrementalEncoder(codecs.IncrementalEncoder): + """Incremental hex encoder""" + + def __init__(self, errors='strict'): + self.errors = errors + self.state = 0 + + def reset(self): + self.state = 0 + + def getstate(self): + return self.state + + def setstate(self, state): + self.state = state + + def encode(self, data, final=False): + """\ + Incremental encode, keep track of digits and emit a byte when a pair + of hex digits is found. The space is optional unless the error + handling is defined to be 'strict'. + """ + state = self.state + encoded = [] + for c in data.upper(): + if c in HEXDIGITS: + z = HEXDIGITS.index(c) + if state: + encoded.append(z + (state & 0xf0)) + state = 0 + else: + state = 0x100 + (z << 4) + elif c == ' ': # allow spaces to separate values + if state and self.errors == 'strict': + raise UnicodeError('odd number of hex digits') + state = 0 + else: + if self.errors == 'strict': + raise UnicodeError('non-hex digit found: {!r}'.format(c)) + self.state = state + return serial.to_bytes(encoded) + + +class IncrementalDecoder(codecs.IncrementalDecoder): + """Incremental decoder""" + def decode(self, data, final=False): + return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) + + +class StreamWriter(Codec, codecs.StreamWriter): + """Combination of hexlify codec and StreamWriter""" + + +class StreamReader(Codec, codecs.StreamReader): + """Combination of hexlify codec and StreamReader""" + + +def getregentry(): + """encodings module API""" + return codecs.CodecInfo( + name='hexlify', + encode=hex_encode, + decode=hex_decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + #~ _is_text_encoding=True, + ) diff --git a/venv/Lib/site-packages/serial/tools/list_ports.py b/venv/Lib/site-packages/serial/tools/list_ports.py new file mode 100644 index 0000000..0d7e3d4 --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/list_ports.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# +# Serial port enumeration. Console tool and backend selection. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +"""\ +This module will provide a function called comports that returns an +iterable (generator or list) that will enumerate available com ports. Note that +on some systems non-existent ports may be listed. + +Additionally a grep function is supplied that can be used to search for ports +based on their descriptions or hardware ID. +""" + +from __future__ import absolute_import + +import sys +import os +import re + +# chose an implementation, depending on os +#~ if sys.platform == 'cli': +#~ else: +if os.name == 'nt': # sys.platform == 'win32': + from serial.tools.list_ports_windows import comports +elif os.name == 'posix': + from serial.tools.list_ports_posix import comports +#~ elif os.name == 'java': +else: + raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def grep(regexp, include_links=False): + """\ + Search for ports using a regular expression. Port name, description and + hardware ID are searched. The function returns an iterable that returns the + same tuples as comport() would do. + """ + r = re.compile(regexp, re.I) + for info in comports(include_links): + port, desc, hwid = info + if r.search(port) or r.search(desc) or r.search(hwid): + yield info + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Serial port enumeration') + + parser.add_argument( + 'regexp', + nargs='?', + help='only show ports that match this regex') + + parser.add_argument( + '-v', '--verbose', + action='store_true', + help='show more messages') + + parser.add_argument( + '-q', '--quiet', + action='store_true', + help='suppress all messages') + + parser.add_argument( + '-n', + type=int, + help='only output the N-th entry') + + parser.add_argument( + '-s', '--include-links', + action='store_true', + help='include entries that are symlinks to real devices') + + args = parser.parse_args() + + hits = 0 + # get iteraror w/ or w/o filter + if args.regexp: + if not args.quiet: + sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp)) + iterator = sorted(grep(args.regexp, include_links=args.include_links)) + else: + iterator = sorted(comports(include_links=args.include_links)) + # list them + for n, (port, desc, hwid) in enumerate(iterator, 1): + if args.n is None or args.n == n: + sys.stdout.write("{:20}\n".format(port)) + if args.verbose: + sys.stdout.write(" desc: {}\n".format(desc)) + sys.stdout.write(" hwid: {}\n".format(hwid)) + hits += 1 + if not args.quiet: + if hits: + sys.stderr.write("{} ports found\n".format(hits)) + else: + sys.stderr.write("no ports found\n") + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + main() diff --git a/venv/Lib/site-packages/serial/tools/list_ports_common.py b/venv/Lib/site-packages/serial/tools/list_ports_common.py new file mode 100644 index 0000000..617f3dc --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/list_ports_common.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# +# This is a helper module for the various platform dependent list_port +# implementations. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import re +import glob +import os +import os.path + + +def numsplit(text): + """\ + Convert string into a list of texts and numbers in order to support a + natural sorting. + """ + result = [] + for group in re.split(r'(\d+)', text): + if group: + try: + group = int(group) + except ValueError: + pass + result.append(group) + return result + + +class ListPortInfo(object): + """Info collection base class for serial ports""" + + def __init__(self, device, skip_link_detection=False): + self.device = device + self.name = os.path.basename(device) + self.description = 'n/a' + self.hwid = 'n/a' + # USB specific data + self.vid = None + self.pid = None + self.serial_number = None + self.location = None + self.manufacturer = None + self.product = None + self.interface = None + # special handling for links + if not skip_link_detection and device is not None and os.path.islink(device): + self.hwid = 'LINK={}'.format(os.path.realpath(device)) + + def usb_description(self): + """return a short string to name the port based on USB info""" + if self.interface is not None: + return '{} - {}'.format(self.product, self.interface) + elif self.product is not None: + return self.product + else: + return self.name + + def usb_info(self): + """return a string with USB related information about device""" + return 'USB VID:PID={:04X}:{:04X}{}{}'.format( + self.vid or 0, + self.pid or 0, + ' SER={}'.format(self.serial_number) if self.serial_number is not None else '', + ' LOCATION={}'.format(self.location) if self.location is not None else '') + + def apply_usb_info(self): + """update description and hwid from USB data""" + self.description = self.usb_description() + self.hwid = self.usb_info() + + def __eq__(self, other): + return isinstance(other, ListPortInfo) and self.device == other.device + + def __hash__(self): + return hash(self.device) + + def __lt__(self, other): + if not isinstance(other, ListPortInfo): + raise TypeError('unorderable types: {}() and {}()'.format( + type(self).__name__, + type(other).__name__)) + return numsplit(self.device) < numsplit(other.device) + + def __str__(self): + return '{} - {}'.format(self.device, self.description) + + def __getitem__(self, index): + """Item access: backwards compatible -> (port, desc, hwid)""" + if index == 0: + return self.device + elif index == 1: + return self.description + elif index == 2: + return self.hwid + else: + raise IndexError('{} > 2'.format(index)) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def list_links(devices): + """\ + search all /dev devices and look for symlinks to known ports already + listed in devices. + """ + links = [] + for device in glob.glob('/dev/*'): + if os.path.islink(device) and os.path.realpath(device) in devices: + links.append(device) + return links + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + print(ListPortInfo('dummy')) diff --git a/venv/Lib/site-packages/serial/tools/list_ports_linux.py b/venv/Lib/site-packages/serial/tools/list_ports_linux.py new file mode 100644 index 0000000..c8c1cfc --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/list_ports_linux.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# This is a module that gathers a list of serial ports including details on +# GNU/Linux systems. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import glob +import os +from serial.tools import list_ports_common + + +class SysFS(list_ports_common.ListPortInfo): + """Wrapper for easy sysfs access and device info""" + + def __init__(self, device): + super(SysFS, self).__init__(device) + # special handling for links + if device is not None and os.path.islink(device): + device = os.path.realpath(device) + is_link = True + else: + is_link = False + self.usb_device_path = None + if os.path.exists('/sys/class/tty/{}/device'.format(self.name)): + self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name)) + self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem'))) + else: + self.device_path = None + self.subsystem = None + # check device type + if self.subsystem == 'usb-serial': + self.usb_interface_path = os.path.dirname(self.device_path) + elif self.subsystem == 'usb': + self.usb_interface_path = self.device_path + else: + self.usb_interface_path = None + # fill-in info for USB devices + if self.usb_interface_path is not None: + self.usb_device_path = os.path.dirname(self.usb_interface_path) + + try: + num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces')) + except ValueError: + num_if = 1 + + self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16) + self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16) + self.serial_number = self.read_line(self.usb_device_path, 'serial') + if num_if > 1: # multi interface devices like FT4232 + self.location = os.path.basename(self.usb_interface_path) + else: + self.location = os.path.basename(self.usb_device_path) + + self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer') + self.product = self.read_line(self.usb_device_path, 'product') + self.interface = self.read_line(self.usb_interface_path, 'interface') + + if self.subsystem in ('usb', 'usb-serial'): + self.apply_usb_info() + #~ elif self.subsystem in ('pnp', 'amba'): # PCI based devices, raspi + elif self.subsystem == 'pnp': # PCI based devices + self.description = self.name + self.hwid = self.read_line(self.device_path, 'id') + elif self.subsystem == 'amba': # raspi + self.description = self.name + self.hwid = os.path.basename(self.device_path) + + if is_link: + self.hwid += ' LINK={}'.format(device) + + def read_line(self, *args): + """\ + Helper function to read a single line from a file. + One or more parameters are allowed, they are joined with os.path.join. + Returns None on errors.. + """ + try: + with open(os.path.join(*args)) as f: + line = f.readline().strip() + return line + except IOError: + return None + + +def comports(include_links=False): + devices = glob.glob('/dev/ttyS*') # built-in serial ports + devices.extend(glob.glob('/dev/ttyUSB*')) # usb-serial with own driver + devices.extend(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial port exar (DELL Edge 3001) + devices.extend(glob.glob('/dev/ttyACM*')) # usb-serial with CDC-ACM profile + devices.extend(glob.glob('/dev/ttyAMA*')) # ARM internal port (raspi) + devices.extend(glob.glob('/dev/rfcomm*')) # BT serial devices + devices.extend(glob.glob('/dev/ttyAP*')) # Advantech multi-port serial controllers + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [info + for info in [SysFS(d) for d in devices] + if info.subsystem != "platform"] # hide non-present internal serial ports + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + for info in sorted(comports()): + print("{0}: {0.subsystem}".format(info)) diff --git a/venv/Lib/site-packages/serial/tools/list_ports_osx.py b/venv/Lib/site-packages/serial/tools/list_ports_osx.py new file mode 100644 index 0000000..51b4e8c --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/list_ports_osx.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python +# +# This is a module that gathers a list of serial ports including details on OSX +# +# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools +# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston +# and modifications by cliechti, hoihu, hardkrash +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2013-2020 +# +# SPDX-License-Identifier: BSD-3-Clause + + +# List all of the callout devices in OS/X by querying IOKit. + +# See the following for a reference of how to do this: +# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD + +# More help from darwin_hid.py + +# Also see the 'IORegistryExplorer' for an idea of what we are actually searching + +from __future__ import absolute_import + +import ctypes + +from serial.tools import list_ports_common + +iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit') +cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation') + +# kIOMasterPortDefault is no longer exported in BigSur but no biggie, using NULL works just the same +kIOMasterPortDefault = 0 # WAS: ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") +kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault") + +kCFStringEncodingMacRoman = 0 +kCFStringEncodingUTF8 = 0x08000100 + +# defined in `IOKit/usb/USBSpec.h` +kUSBVendorString = 'USB Vendor Name' +kUSBSerialNumberString = 'USB Serial Number' + +# `io_name_t` defined as `typedef char io_name_t[128];` +# in `device/device_types.h` +io_name_size = 128 + +# defined in `mach/kern_return.h` +KERN_SUCCESS = 0 +# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h` +kern_return_t = ctypes.c_int + +iokit.IOServiceMatching.restype = ctypes.c_void_p + +iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IOServiceGetMatchingServices.restype = kern_return_t + +iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IOServiceGetMatchingServices.restype = kern_return_t + +iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] +iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p + +iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IORegistryEntryGetPath.restype = kern_return_t + +iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +iokit.IORegistryEntryGetName.restype = kern_return_t + +iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +iokit.IOObjectGetClass.restype = kern_return_t + +iokit.IOObjectRelease.argtypes = [ctypes.c_void_p] + + +cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32] +cf.CFStringCreateWithCString.restype = ctypes.c_void_p + +cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32] +cf.CFStringGetCStringPtr.restype = ctypes.c_char_p + +cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32] +cf.CFStringGetCString.restype = ctypes.c_bool + +cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p] +cf.CFNumberGetValue.restype = ctypes.c_void_p + +# void CFRelease ( CFTypeRef cf ); +cf.CFRelease.argtypes = [ctypes.c_void_p] +cf.CFRelease.restype = None + +# CFNumber type defines +kCFNumberSInt8Type = 1 +kCFNumberSInt16Type = 2 +kCFNumberSInt32Type = 3 +kCFNumberSInt64Type = 4 + + +def get_string_property(device_type, property): + """ + Search the given device for the specified string property + + @param device_type Type of Device + @param property String to search for + @return Python string containing the value, or None if not found. + """ + key = cf.CFStringCreateWithCString( + kCFAllocatorDefault, + property.encode("utf-8"), + kCFStringEncodingUTF8) + + CFContainer = iokit.IORegistryEntryCreateCFProperty( + device_type, + key, + kCFAllocatorDefault, + 0) + output = None + + if CFContainer: + output = cf.CFStringGetCStringPtr(CFContainer, 0) + if output is not None: + output = output.decode('utf-8') + else: + buffer = ctypes.create_string_buffer(io_name_size); + success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8) + if success: + output = buffer.value.decode('utf-8') + cf.CFRelease(CFContainer) + return output + + +def get_int_property(device_type, property, cf_number_type): + """ + Search the given device for the specified string property + + @param device_type Device to search + @param property String to search for + @param cf_number_type CFType number + + @return Python string containing the value, or None if not found. + """ + key = cf.CFStringCreateWithCString( + kCFAllocatorDefault, + property.encode("utf-8"), + kCFStringEncodingUTF8) + + CFContainer = iokit.IORegistryEntryCreateCFProperty( + device_type, + key, + kCFAllocatorDefault, + 0) + + if CFContainer: + if (cf_number_type == kCFNumberSInt32Type): + number = ctypes.c_uint32() + elif (cf_number_type == kCFNumberSInt16Type): + number = ctypes.c_uint16() + cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number)) + cf.CFRelease(CFContainer) + return number.value + return None + +def IORegistryEntryGetName(device): + devicename = ctypes.create_string_buffer(io_name_size); + res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename)) + if res != KERN_SUCCESS: + return None + # this works in python2 but may not be valid. Also I don't know if + # this encoding is guaranteed. It may be dependent on system locale. + return devicename.value.decode('utf-8') + +def IOObjectGetClass(device): + classname = ctypes.create_string_buffer(io_name_size) + iokit.IOObjectGetClass(device, ctypes.byref(classname)) + return classname.value + +def GetParentDeviceByType(device, parent_type): + """ Find the first parent of a device that implements the parent_type + @param IOService Service to inspect + @return Pointer to the parent type, or None if it was not found. + """ + # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice. + parent_type = parent_type.encode('utf-8') + while IOObjectGetClass(device) != parent_type: + parent = ctypes.c_void_p() + response = iokit.IORegistryEntryGetParentEntry( + device, + "IOService".encode("utf-8"), + ctypes.byref(parent)) + # If we weren't able to find a parent for the device, we're done. + if response != KERN_SUCCESS: + return None + device = parent + return device + + +def GetIOServicesByType(service_type): + """ + returns iterator over specified service_type + """ + serial_port_iterator = ctypes.c_void_p() + + iokit.IOServiceGetMatchingServices( + kIOMasterPortDefault, + iokit.IOServiceMatching(service_type.encode('utf-8')), + ctypes.byref(serial_port_iterator)) + + services = [] + while iokit.IOIteratorIsValid(serial_port_iterator): + service = iokit.IOIteratorNext(serial_port_iterator) + if not service: + break + services.append(service) + iokit.IOObjectRelease(serial_port_iterator) + return services + + +def location_to_string(locationID): + """ + helper to calculate port and bus number from locationID + """ + loc = ['{}-'.format(locationID >> 24)] + while locationID & 0xf00000: + if len(loc) > 1: + loc.append('.') + loc.append('{}'.format((locationID >> 20) & 0xf)) + locationID <<= 4 + return ''.join(loc) + + +class SuitableSerialInterface(object): + pass + + +def scan_interfaces(): + """ + helper function to scan USB interfaces + returns a list of SuitableSerialInterface objects with name and id attributes + """ + interfaces = [] + for service in GetIOServicesByType('IOSerialBSDClient'): + device = get_string_property(service, "IOCalloutDevice") + if device: + usb_device = GetParentDeviceByType(service, "IOUSBInterface") + if usb_device: + name = get_string_property(usb_device, "USB Interface Name") or None + locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or '' + i = SuitableSerialInterface() + i.id = locationID + i.name = name + interfaces.append(i) + return interfaces + + +def search_for_locationID_in_interfaces(serial_interfaces, locationID): + for interface in serial_interfaces: + if (interface.id == locationID): + return interface.name + return None + + +def comports(include_links=False): + # XXX include_links is currently ignored. are links in /dev even supported here? + # Scan for all iokit serial ports + services = GetIOServicesByType('IOSerialBSDClient') + ports = [] + serial_interfaces = scan_interfaces() + for service in services: + # First, add the callout device file. + device = get_string_property(service, "IOCalloutDevice") + if device: + info = list_ports_common.ListPortInfo(device) + # If the serial port is implemented by IOUSBDevice + # NOTE IOUSBDevice was deprecated as of 10.11 and finally on Apple Silicon + # devices has been completely removed. Thanks to @oskay for this patch. + usb_device = GetParentDeviceByType(service, "IOUSBHostDevice") + if not usb_device: + usb_device = GetParentDeviceByType(service, "IOUSBDevice") + if usb_device: + # fetch some useful informations from properties + info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type) + info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type) + info.serial_number = get_string_property(usb_device, kUSBSerialNumberString) + # We know this is a usb device, so the + # IORegistryEntryName should always be aliased to the + # usb product name string descriptor. + info.product = IORegistryEntryGetName(usb_device) or 'n/a' + info.manufacturer = get_string_property(usb_device, kUSBVendorString) + locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) + info.location = location_to_string(locationID) + info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID) + info.apply_usb_info() + ports.append(info) + return ports + +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print("{}: {} [{}]".format(port, desc, hwid)) diff --git a/venv/Lib/site-packages/serial/tools/list_ports_posix.py b/venv/Lib/site-packages/serial/tools/list_ports_posix.py new file mode 100644 index 0000000..79bc8ed --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/list_ports_posix.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# +# This is a module that gathers a list of serial ports on POSIXy systems. +# For some specific implementations, see also list_ports_linux, list_ports_osx +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +"""\ +The ``comports`` function is expected to return an iterable that yields tuples +of 3 strings: port name, human readable description and a hardware ID. + +As currently no method is known to get the second two strings easily, they are +currently just identical to the port name. +""" + +from __future__ import absolute_import + +import glob +import sys +import os +from serial.tools import list_ports_common + +# try to detect the OS so that a device can be selected... +plat = sys.platform.lower() + +if plat[:5] == 'linux': # Linux (confirmed) # noqa + from serial.tools.list_ports_linux import comports + +elif plat[:6] == 'darwin': # OS X (confirmed) + from serial.tools.list_ports_osx import comports + +elif plat == 'cygwin': # cygwin/win32 + # cygwin accepts /dev/com* in many contexts + # (such as 'open' call, explicit 'ls'), but 'glob.glob' + # and bare 'ls' do not; so use /dev/ttyS* instead + def comports(include_links=False): + devices = glob.glob('/dev/ttyS*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:7] == 'openbsd': # OpenBSD + def comports(include_links=False): + devices = glob.glob('/dev/cua*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:3] == 'bsd' or plat[:7] == 'freebsd': + def comports(include_links=False): + devices = glob.glob('/dev/cua*[!.init][!.lock]') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:6] == 'netbsd': # NetBSD + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/dty*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:4] == 'irix': # IRIX + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/ttyf*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:2] == 'hp': # HP-UX (not tested) + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/tty*p0') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:5] == 'sunos': # Solaris/SunOS + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/tty*c') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +elif plat[:3] == 'aix': # AIX + def comports(include_links=False): + """scan for available ports. return a list of device names.""" + devices = glob.glob('/dev/tty*') + if include_links: + devices.extend(list_ports_common.list_links(devices)) + return [list_ports_common.ListPortInfo(d) for d in devices] + +else: + # platform detection has failed... + import serial + sys.stderr.write("""\ +don't know how to enumerate ttys on this system. +! I you know how the serial ports are named send this information to +! the author of this module: + +sys.platform = {!r} +os.name = {!r} +pySerial version = {} + +also add the naming scheme of the serial ports and with a bit luck you can get +this module running... +""".format(sys.platform, os.name, serial.VERSION)) + raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name)) + +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print("{}: {} [{}]".format(port, desc, hwid)) diff --git a/venv/Lib/site-packages/serial/tools/list_ports_windows.py b/venv/Lib/site-packages/serial/tools/list_ports_windows.py new file mode 100644 index 0000000..0b4a5b1 --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/list_ports_windows.py @@ -0,0 +1,427 @@ +#! python +# +# Enumerate serial ports on Windows including a human readable description +# and hardware information. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2016 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +# pylint: disable=invalid-name,too-few-public-methods +import re +import ctypes +from ctypes.wintypes import BOOL +from ctypes.wintypes import HWND +from ctypes.wintypes import DWORD +from ctypes.wintypes import WORD +from ctypes.wintypes import LONG +from ctypes.wintypes import ULONG +from ctypes.wintypes import HKEY +from ctypes.wintypes import BYTE +import serial +from serial.win32 import ULONG_PTR +from serial.tools import list_ports_common + + +def ValidHandle(value, func, arguments): + if value == 0: + raise ctypes.WinError() + return value + + +NULL = 0 +HDEVINFO = ctypes.c_void_p +LPCTSTR = ctypes.c_wchar_p +PCTSTR = ctypes.c_wchar_p +PTSTR = ctypes.c_wchar_p +LPDWORD = PDWORD = ctypes.POINTER(DWORD) +#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE) +LPBYTE = PBYTE = ctypes.c_void_p # XXX avoids error about types + +ACCESS_MASK = DWORD +REGSAM = ACCESS_MASK + + +class GUID(ctypes.Structure): + _fields_ = [ + ('Data1', DWORD), + ('Data2', WORD), + ('Data3', WORD), + ('Data4', BYTE * 8), + ] + + def __str__(self): + return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format( + self.Data1, + self.Data2, + self.Data3, + ''.join(["{:02x}".format(d) for d in self.Data4[:2]]), + ''.join(["{:02x}".format(d) for d in self.Data4[2:]]), + ) + + +class SP_DEVINFO_DATA(ctypes.Structure): + _fields_ = [ + ('cbSize', DWORD), + ('ClassGuid', GUID), + ('DevInst', DWORD), + ('Reserved', ULONG_PTR), + ] + + def __str__(self): + return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst) + + +PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA) + +PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p + +setupapi = ctypes.windll.LoadLibrary("setupapi") +SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList +SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] +SetupDiDestroyDeviceInfoList.restype = BOOL + +SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW +SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD] +SetupDiClassGuidsFromName.restype = BOOL + +SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo +SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA] +SetupDiEnumDeviceInfo.restype = BOOL + +SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW +SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] +SetupDiGetClassDevs.restype = HDEVINFO +SetupDiGetClassDevs.errcheck = ValidHandle + +SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW +SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD] +SetupDiGetDeviceRegistryProperty.restype = BOOL + +SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW +SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD] +SetupDiGetDeviceInstanceId.restype = BOOL + +SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey +SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM] +SetupDiOpenDevRegKey.restype = HKEY + +advapi32 = ctypes.windll.LoadLibrary("Advapi32") +RegCloseKey = advapi32.RegCloseKey +RegCloseKey.argtypes = [HKEY] +RegCloseKey.restype = LONG + +RegQueryValueEx = advapi32.RegQueryValueExW +RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD] +RegQueryValueEx.restype = LONG + +cfgmgr32 = ctypes.windll.LoadLibrary("Cfgmgr32") +CM_Get_Parent = cfgmgr32.CM_Get_Parent +CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG] +CM_Get_Parent.restype = LONG + +CM_Get_Device_IDW = cfgmgr32.CM_Get_Device_IDW +CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG] +CM_Get_Device_IDW.restype = LONG + +CM_MapCrToWin32Err = cfgmgr32.CM_MapCrToWin32Err +CM_MapCrToWin32Err.argtypes = [DWORD, DWORD] +CM_MapCrToWin32Err.restype = DWORD + + +DIGCF_PRESENT = 2 +DIGCF_DEVICEINTERFACE = 16 +INVALID_HANDLE_VALUE = 0 +ERROR_INSUFFICIENT_BUFFER = 122 +ERROR_NOT_FOUND = 1168 +SPDRP_HARDWAREID = 1 +SPDRP_FRIENDLYNAME = 12 +SPDRP_LOCATION_PATHS = 35 +SPDRP_MFG = 11 +DICS_FLAG_GLOBAL = 1 +DIREG_DEV = 0x00000001 +KEY_READ = 0x20019 + + +MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5 + + +def get_parent_serial_number(child_devinst, child_vid, child_pid, depth=0, last_serial_number=None): + """ Get the serial number of the parent of a device. + + Args: + child_devinst: The device instance handle to get the parent serial number of. + child_vid: The vendor ID of the child device. + child_pid: The product ID of the child device. + depth: The current iteration depth of the USB device tree. + """ + + # If the traversal depth is beyond the max, abandon attempting to find the serial number. + if depth > MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH: + return '' if not last_serial_number else last_serial_number + + # Get the parent device instance. + devinst = DWORD() + ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0) + + if ret: + win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0)) + + # If there is no parent available, the child was the root device. We cannot traverse + # further. + if win_error == ERROR_NOT_FOUND: + return '' if not last_serial_number else last_serial_number + + raise ctypes.WinError(win_error) + + # Get the ID of the parent device and parse it for vendor ID, product ID, and serial number. + parentHardwareID = ctypes.create_unicode_buffer(250) + + ret = CM_Get_Device_IDW( + devinst, + parentHardwareID, + ctypes.sizeof(parentHardwareID) - 1, + 0) + + if ret: + raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0))) + + parentHardwareID_str = parentHardwareID.value + m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', + parentHardwareID_str, + re.I) + + # return early if we have no matches (likely malformed serial, traversed too far) + if not m: + return '' if not last_serial_number else last_serial_number + + vid = None + pid = None + serial_number = None + if m.group(1): + vid = int(m.group(1), 16) + if m.group(3): + pid = int(m.group(3), 16) + if m.group(7): + serial_number = m.group(7) + + # store what we found as a fallback for malformed serial values up the chain + found_serial_number = serial_number + + # Check that the USB serial number only contains alpha-numeric characters. It may be a windows + # device ID (ephemeral ID). + if serial_number and not re.match(r'^\w+$', serial_number): + serial_number = None + + if not vid or not pid: + # If pid and vid are not available at this device level, continue to the parent. + return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number) + + if pid != child_pid or vid != child_vid: + # If the VID or PID has changed, we are no longer looking at the same physical device. The + # serial number is unknown. + return '' if not last_serial_number else last_serial_number + + # In this case, the vid and pid of the parent device are identical to the child. However, if + # there still isn't a serial number available, continue to the next parent. + if not serial_number: + return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number) + + # Finally, the VID and PID are identical to the child and a serial number is present, so return + # it. + return serial_number + + +def iterate_comports(): + """Return a generator that yields descriptions for serial ports""" + PortsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... + ports_guids_size = DWORD() + if not SetupDiClassGuidsFromName( + "Ports", + PortsGUIDs, + ctypes.sizeof(PortsGUIDs), + ctypes.byref(ports_guids_size)): + raise ctypes.WinError() + + ModemsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... + modems_guids_size = DWORD() + if not SetupDiClassGuidsFromName( + "Modem", + ModemsGUIDs, + ctypes.sizeof(ModemsGUIDs), + ctypes.byref(modems_guids_size)): + raise ctypes.WinError() + + GUIDs = PortsGUIDs[:ports_guids_size.value] + ModemsGUIDs[:modems_guids_size.value] + + # repeat for all possible GUIDs + for index in range(len(GUIDs)): + bInterfaceNumber = None + g_hdi = SetupDiGetClassDevs( + ctypes.byref(GUIDs[index]), + None, + NULL, + DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports + + devinfo = SP_DEVINFO_DATA() + devinfo.cbSize = ctypes.sizeof(devinfo) + index = 0 + while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)): + index += 1 + + # get the real com port name + hkey = SetupDiOpenDevRegKey( + g_hdi, + ctypes.byref(devinfo), + DICS_FLAG_GLOBAL, + 0, + DIREG_DEV, # DIREG_DRV for SW info + KEY_READ) + port_name_buffer = ctypes.create_unicode_buffer(250) + port_name_length = ULONG(ctypes.sizeof(port_name_buffer)) + RegQueryValueEx( + hkey, + "PortName", + None, + None, + ctypes.byref(port_name_buffer), + ctypes.byref(port_name_length)) + RegCloseKey(hkey) + + # unfortunately does this method also include parallel ports. + # we could check for names starting with COM or just exclude LPT + # and hope that other "unknown" names are serial ports... + if port_name_buffer.value.startswith('LPT'): + continue + + # hardware ID + szHardwareID = ctypes.create_unicode_buffer(250) + # try to get ID that includes serial number + if not SetupDiGetDeviceInstanceId( + g_hdi, + ctypes.byref(devinfo), + #~ ctypes.byref(szHardwareID), + szHardwareID, + ctypes.sizeof(szHardwareID) - 1, + None): + # fall back to more generic hardware ID if that would fail + if not SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_HARDWAREID, + None, + ctypes.byref(szHardwareID), + ctypes.sizeof(szHardwareID) - 1, + None): + # Ignore ERROR_INSUFFICIENT_BUFFER + if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: + raise ctypes.WinError() + # stringify + szHardwareID_str = szHardwareID.value + + info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True) + + # in case of USB, make a more readable string, similar to that form + # that we also generate on other platforms + if szHardwareID_str.startswith('USB'): + m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', szHardwareID_str, re.I) + if m: + info.vid = int(m.group(1), 16) + if m.group(3): + info.pid = int(m.group(3), 16) + if m.group(5): + bInterfaceNumber = int(m.group(5)) + + # Check that the USB serial number only contains alpha-numeric characters. It + # may be a windows device ID (ephemeral ID) for composite devices. + if m.group(7) and re.match(r'^\w+$', m.group(7)): + info.serial_number = m.group(7) + else: + info.serial_number = get_parent_serial_number(devinfo.DevInst, info.vid, info.pid) + + # calculate a location string + loc_path_str = ctypes.create_unicode_buffer(250) + if SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_LOCATION_PATHS, + None, + ctypes.byref(loc_path_str), + ctypes.sizeof(loc_path_str) - 1, + None): + m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', loc_path_str.value) + location = [] + for g in m: + if g.group(1): + location.append('{:d}'.format(int(g.group(1)) + 1)) + else: + if len(location) > 1: + location.append('.') + else: + location.append('-') + location.append(g.group(2)) + if bInterfaceNumber is not None: + location.append(':{}.{}'.format( + 'x', # XXX how to determine correct bConfigurationValue? + bInterfaceNumber)) + if location: + info.location = ''.join(location) + info.hwid = info.usb_info() + elif szHardwareID_str.startswith('FTDIBUS'): + m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I) + if m: + info.vid = int(m.group(1), 16) + info.pid = int(m.group(2), 16) + if m.group(4): + info.serial_number = m.group(4) + # USB location is hidden by FDTI driver :( + info.hwid = info.usb_info() + else: + info.hwid = szHardwareID_str + + # friendly name + szFriendlyName = ctypes.create_unicode_buffer(250) + if SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_FRIENDLYNAME, + #~ SPDRP_DEVICEDESC, + None, + ctypes.byref(szFriendlyName), + ctypes.sizeof(szFriendlyName) - 1, + None): + info.description = szFriendlyName.value + #~ else: + # Ignore ERROR_INSUFFICIENT_BUFFER + #~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: + #~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value)) + # ignore errors and still include the port in the list, friendly name will be same as port name + + # manufacturer + szManufacturer = ctypes.create_unicode_buffer(250) + if SetupDiGetDeviceRegistryProperty( + g_hdi, + ctypes.byref(devinfo), + SPDRP_MFG, + #~ SPDRP_DEVICEDESC, + None, + ctypes.byref(szManufacturer), + ctypes.sizeof(szManufacturer) - 1, + None): + info.manufacturer = szManufacturer.value + yield info + SetupDiDestroyDeviceInfoList(g_hdi) + + +def comports(include_links=False): + """Return a list of info objects about serial ports""" + return list(iterate_comports()) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print("{}: {} [{}]".format(port, desc, hwid)) diff --git a/venv/Lib/site-packages/serial/tools/miniterm.py b/venv/Lib/site-packages/serial/tools/miniterm.py new file mode 100644 index 0000000..2cceff6 --- /dev/null +++ b/venv/Lib/site-packages/serial/tools/miniterm.py @@ -0,0 +1,1042 @@ +#!/usr/bin/env python +# +# Very simple serial terminal +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C)2002-2020 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import absolute_import + +import codecs +import os +import sys +import threading + +import serial +from serial.tools.list_ports import comports +from serial.tools import hexlify_codec + +# pylint: disable=wrong-import-order,wrong-import-position + +codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None) + +try: + raw_input +except NameError: + # pylint: disable=redefined-builtin,invalid-name + raw_input = input # in python3 it's "raw" + unichr = chr + + +def key_description(character): + """generate a readable description for a key""" + ascii_code = ord(character) + if ascii_code < 32: + return 'Ctrl+{:c}'.format(ord('@') + ascii_code) + else: + return repr(character) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class ConsoleBase(object): + """OS abstraction for console (input/output codec, no echo)""" + + def __init__(self): + if sys.version_info >= (3, 0): + self.byte_output = sys.stdout.buffer + else: + self.byte_output = sys.stdout + self.output = sys.stdout + + def setup(self): + """Set console to read single characters, no echo""" + + def cleanup(self): + """Restore default console settings""" + + def getkey(self): + """Read a single key from the console""" + return None + + def write_bytes(self, byte_string): + """Write bytes (already encoded)""" + self.byte_output.write(byte_string) + self.byte_output.flush() + + def write(self, text): + """Write string""" + self.output.write(text) + self.output.flush() + + def cancel(self): + """Cancel getkey operation""" + + # - - - - - - - - - - - - - - - - - - - - - - - - + # context manager: + # switch terminal temporary to normal mode (e.g. to get user input) + + def __enter__(self): + self.cleanup() + return self + + def __exit__(self, *args, **kwargs): + self.setup() + + +if os.name == 'nt': # noqa + import msvcrt + import ctypes + import platform + + class Out(object): + """file-like wrapper that uses os.write""" + + def __init__(self, fd): + self.fd = fd + + def flush(self): + pass + + def write(self, s): + os.write(self.fd, s) + + class Console(ConsoleBase): + fncodes = { + ';': '\1bOP', # F1 + '<': '\1bOQ', # F2 + '=': '\1bOR', # F3 + '>': '\1bOS', # F4 + '?': '\1b[15~', # F5 + '@': '\1b[17~', # F6 + 'A': '\1b[18~', # F7 + 'B': '\1b[19~', # F8 + 'C': '\1b[20~', # F9 + 'D': '\1b[21~', # F10 + } + navcodes = { + 'H': '\x1b[A', # UP + 'P': '\x1b[B', # DOWN + 'K': '\x1b[D', # LEFT + 'M': '\x1b[C', # RIGHT + 'G': '\x1b[H', # HOME + 'O': '\x1b[F', # END + 'R': '\x1b[2~', # INSERT + 'S': '\x1b[3~', # DELETE + 'I': '\x1b[5~', # PGUP + 'Q': '\x1b[6~', # PGDN + } + + def __init__(self): + super(Console, self).__init__() + self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP() + self._saved_icp = ctypes.windll.kernel32.GetConsoleCP() + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + ctypes.windll.kernel32.SetConsoleCP(65001) + # ANSI handling available through SetConsoleMode since Windows 10 v1511 + # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1 + if platform.release() == '10' and int(platform.version().split('.')[2]) > 10586: + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + import ctypes.wintypes as wintypes + if not hasattr(wintypes, 'LPDWORD'): # PY2 + wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) + SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode + GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode + GetStdHandle = ctypes.windll.kernel32.GetStdHandle + mode = wintypes.DWORD() + GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode)) + if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: + SetConsoleMode(GetStdHandle(-11), mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self._saved_cm = mode + self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace') + # the change of the code page is not propagated to Python, manually fix it + sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace') + sys.stdout = self.output + self.output.encoding = 'UTF-8' # needed for input + + def __del__(self): + ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp) + ctypes.windll.kernel32.SetConsoleCP(self._saved_icp) + try: + ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm) + except AttributeError: # in case no _saved_cm + pass + + def getkey(self): + while True: + z = msvcrt.getwch() + if z == unichr(13): + return unichr(10) + elif z is unichr(0) or z is unichr(0xe0): + try: + code = msvcrt.getwch() + if z is unichr(0): + return self.fncodes[code] + else: + return self.navcodes[code] + except KeyError: + pass + else: + return z + + def cancel(self): + # CancelIo, CancelSynchronousIo do not seem to work when using + # getwch, so instead, send a key to the window with the console + hwnd = ctypes.windll.kernel32.GetConsoleWindow() + ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0) + +elif os.name == 'posix': + import atexit + import termios + import fcntl + + class Console(ConsoleBase): + def __init__(self): + super(Console, self).__init__() + self.fd = sys.stdin.fileno() + self.old = termios.tcgetattr(self.fd) + atexit.register(self.cleanup) + if sys.version_info < (3, 0): + self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin) + else: + self.enc_stdin = sys.stdin + + def setup(self): + new = termios.tcgetattr(self.fd) + new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG + new[6][termios.VMIN] = 1 + new[6][termios.VTIME] = 0 + termios.tcsetattr(self.fd, termios.TCSANOW, new) + + def getkey(self): + c = self.enc_stdin.read(1) + if c == unichr(0x7f): + c = unichr(8) # map the BS key (which yields DEL) to backspace + return c + + def cancel(self): + fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0') + + def cleanup(self): + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) + +else: + raise NotImplementedError( + 'Sorry no implementation for your platform ({}) available.'.format(sys.platform)) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +class Transform(object): + """do-nothing: forward all data unchanged""" + def rx(self, text): + """text received from serial port""" + return text + + def tx(self, text): + """text to be sent to serial port""" + return text + + def echo(self, text): + """text to be sent but displayed on console""" + return text + + +class CRLF(Transform): + """ENTER sends CR+LF""" + + def tx(self, text): + return text.replace('\n', '\r\n') + + +class CR(Transform): + """ENTER sends CR""" + + def rx(self, text): + return text.replace('\r', '\n') + + def tx(self, text): + return text.replace('\n', '\r') + + +class LF(Transform): + """ENTER sends LF""" + + +class NoTerminal(Transform): + """remove typical terminal control codes from input""" + + REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t') + REPLACEMENT_MAP.update( + { + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + }) + + def rx(self, text): + return text.translate(self.REPLACEMENT_MAP) + + echo = rx + + +class NoControls(NoTerminal): + """Remove all control codes, incl. CR+LF""" + + REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32)) + REPLACEMENT_MAP.update( + { + 0x20: 0x2423, # visual space + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + }) + + +class Printable(Transform): + """Show decimal code for all non-ASCII characters and replace most control codes""" + + def rx(self, text): + r = [] + for c in text: + if ' ' <= c < '\x7f' or c in '\r\n\b\t': + r.append(c) + elif c < ' ': + r.append(unichr(0x2400 + ord(c))) + else: + r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c))) + r.append(' ') + return ''.join(r) + + echo = rx + + +class Colorize(Transform): + """Apply different colors for received and echo""" + + def __init__(self): + # XXX make it configurable, use colorama? + self.input_color = '\x1b[37m' + self.echo_color = '\x1b[31m' + + def rx(self, text): + return self.input_color + text + + def echo(self, text): + return self.echo_color + text + + +class DebugIO(Transform): + """Print what is sent and received""" + + def rx(self, text): + sys.stderr.write(' [RX:{!r}] '.format(text)) + sys.stderr.flush() + return text + + def tx(self, text): + sys.stderr.write(' [TX:{!r}] '.format(text)) + sys.stderr.flush() + return text + + +# other ideas: +# - add date/time for each newline +# - insert newline after: a) timeout b) packet end character + +EOL_TRANSFORMATIONS = { + 'crlf': CRLF, + 'cr': CR, + 'lf': LF, +} + +TRANSFORMATIONS = { + 'direct': Transform, # no transformation + 'default': NoTerminal, + 'nocontrol': NoControls, + 'printable': Printable, + 'colorize': Colorize, + 'debug': DebugIO, +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def ask_for_port(): + """\ + Show a list of ports and ask the user for a choice. To make selection + easier on systems with long device names, also allow the input of an + index. + """ + sys.stderr.write('\n--- Available ports:\n') + ports = [] + for n, (port, desc, hwid) in enumerate(sorted(comports()), 1): + sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc)) + ports.append(port) + while True: + port = raw_input('--- Enter port index or full name: ') + try: + index = int(port) - 1 + if not 0 <= index < len(ports): + sys.stderr.write('--- Invalid index!\n') + continue + except ValueError: + pass + else: + port = ports[index] + return port + + +class Miniterm(object): + """\ + Terminal application. Copy data from serial port to console and vice versa. + Handle special keys from the console to show menu etc. + """ + + def __init__(self, serial_instance, echo=False, eol='crlf', filters=()): + self.console = Console() + self.serial = serial_instance + self.echo = echo + self.raw = False + self.input_encoding = 'UTF-8' + self.output_encoding = 'UTF-8' + self.eol = eol + self.filters = filters + self.update_transformations() + self.exit_character = unichr(0x1d) # GS/CTRL+] + self.menu_character = unichr(0x14) # Menu: CTRL+T + self.alive = None + self._reader_alive = None + self.receiver_thread = None + self.rx_decoder = None + self.tx_decoder = None + + def _start_reader(self): + """Start reader thread""" + self._reader_alive = True + # start serial->console thread + self.receiver_thread = threading.Thread(target=self.reader, name='rx') + self.receiver_thread.daemon = True + self.receiver_thread.start() + + def _stop_reader(self): + """Stop reader thread only, wait for clean exit of thread""" + self._reader_alive = False + if hasattr(self.serial, 'cancel_read'): + self.serial.cancel_read() + self.receiver_thread.join() + + def start(self): + """start worker threads""" + self.alive = True + self._start_reader() + # enter console->serial loop + self.transmitter_thread = threading.Thread(target=self.writer, name='tx') + self.transmitter_thread.daemon = True + self.transmitter_thread.start() + self.console.setup() + + def stop(self): + """set flag to stop worker threads""" + self.alive = False + + def join(self, transmit_only=False): + """wait for worker threads to terminate""" + self.transmitter_thread.join() + if not transmit_only: + if hasattr(self.serial, 'cancel_read'): + self.serial.cancel_read() + self.receiver_thread.join() + + def close(self): + self.serial.close() + + def update_transformations(self): + """take list of transformation classes and instantiate them for rx and tx""" + transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] + for f in self.filters] + self.tx_transformations = [t() for t in transformations] + self.rx_transformations = list(reversed(self.tx_transformations)) + + def set_rx_encoding(self, encoding, errors='replace'): + """set encoding for received data""" + self.input_encoding = encoding + self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors) + + def set_tx_encoding(self, encoding, errors='replace'): + """set encoding for transmitted data""" + self.output_encoding = encoding + self.tx_encoder = codecs.getincrementalencoder(encoding)(errors) + + def dump_port_settings(self): + """Write current settings to sys.stderr""" + sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format( + p=self.serial)) + sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format( + ('active' if self.serial.rts else 'inactive'), + ('active' if self.serial.dtr else 'inactive'), + ('active' if self.serial.break_condition else 'inactive'))) + try: + sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format( + ('active' if self.serial.cts else 'inactive'), + ('active' if self.serial.dsr else 'inactive'), + ('active' if self.serial.ri else 'inactive'), + ('active' if self.serial.cd else 'inactive'))) + except serial.SerialException: + # on RFC 2217 ports, it can happen if no modem state notification was + # yet received. ignore this error. + pass + sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive')) + sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive')) + sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding)) + sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding)) + sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper())) + sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters))) + + def reader(self): + """loop and copy serial->console""" + try: + while self.alive and self._reader_alive: + # read all that is there or wait for one byte + data = self.serial.read(self.serial.in_waiting or 1) + if data: + if self.raw: + self.console.write_bytes(data) + else: + text = self.rx_decoder.decode(data) + for transformation in self.rx_transformations: + text = transformation.rx(text) + self.console.write(text) + except serial.SerialException: + self.alive = False + self.console.cancel() + raise # XXX handle instead of re-raise? + + def writer(self): + """\ + Loop and copy console->serial until self.exit_character character is + found. When self.menu_character is found, interpret the next key + locally. + """ + menu_active = False + try: + while self.alive: + try: + c = self.console.getkey() + except KeyboardInterrupt: + c = '\x03' + if not self.alive: + break + if menu_active: + self.handle_menu_key(c) + menu_active = False + elif c == self.menu_character: + menu_active = True # next char will be for menu + elif c == self.exit_character: + self.stop() # exit app + break + else: + #~ if self.raw: + text = c + for transformation in self.tx_transformations: + text = transformation.tx(text) + self.serial.write(self.tx_encoder.encode(text)) + if self.echo: + echo_text = c + for transformation in self.tx_transformations: + echo_text = transformation.echo(echo_text) + self.console.write(echo_text) + except: + self.alive = False + raise + + def handle_menu_key(self, c): + """Implement a simple menu / settings""" + if c == self.menu_character or c == self.exit_character: + # Menu/exit character again -> send itself + self.serial.write(self.tx_encoder.encode(c)) + if self.echo: + self.console.write(c) + elif c == '\x15': # CTRL+U -> upload file + self.upload_file() + elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help + sys.stderr.write(self.get_help_text()) + elif c == '\x12': # CTRL+R -> Toggle RTS + self.serial.rts = not self.serial.rts + sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive')) + elif c == '\x04': # CTRL+D -> Toggle DTR + self.serial.dtr = not self.serial.dtr + sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive')) + elif c == '\x02': # CTRL+B -> toggle BREAK condition + self.serial.break_condition = not self.serial.break_condition + sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive')) + elif c == '\x05': # CTRL+E -> toggle local echo + self.echo = not self.echo + sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive')) + elif c == '\x06': # CTRL+F -> edit filters + self.change_filter() + elif c == '\x0c': # CTRL+L -> EOL mode + modes = list(EOL_TRANSFORMATIONS) # keys + eol = modes.index(self.eol) + 1 + if eol >= len(modes): + eol = 0 + self.eol = modes[eol] + sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper())) + self.update_transformations() + elif c == '\x01': # CTRL+A -> set encoding + self.change_encoding() + elif c == '\x09': # CTRL+I -> info + self.dump_port_settings() + #~ elif c == '\x01': # CTRL+A -> cycle escape mode + #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode + elif c in 'pP': # P -> change port + self.change_port() + elif c in 'zZ': # S -> suspend / open port temporarily + self.suspend_port() + elif c in 'bB': # B -> change baudrate + self.change_baudrate() + elif c == '8': # 8 -> change to 8 bits + self.serial.bytesize = serial.EIGHTBITS + self.dump_port_settings() + elif c == '7': # 7 -> change to 8 bits + self.serial.bytesize = serial.SEVENBITS + self.dump_port_settings() + elif c in 'eE': # E -> change to even parity + self.serial.parity = serial.PARITY_EVEN + self.dump_port_settings() + elif c in 'oO': # O -> change to odd parity + self.serial.parity = serial.PARITY_ODD + self.dump_port_settings() + elif c in 'mM': # M -> change to mark parity + self.serial.parity = serial.PARITY_MARK + self.dump_port_settings() + elif c in 'sS': # S -> change to space parity + self.serial.parity = serial.PARITY_SPACE + self.dump_port_settings() + elif c in 'nN': # N -> change to no parity + self.serial.parity = serial.PARITY_NONE + self.dump_port_settings() + elif c == '1': # 1 -> change to 1 stop bits + self.serial.stopbits = serial.STOPBITS_ONE + self.dump_port_settings() + elif c == '2': # 2 -> change to 2 stop bits + self.serial.stopbits = serial.STOPBITS_TWO + self.dump_port_settings() + elif c == '3': # 3 -> change to 1.5 stop bits + self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE + self.dump_port_settings() + elif c in 'xX': # X -> change software flow control + self.serial.xonxoff = (c == 'X') + self.dump_port_settings() + elif c in 'rR': # R -> change hardware flow control + self.serial.rtscts = (c == 'R') + self.dump_port_settings() + elif c in 'qQ': + self.stop() # Q -> exit app + else: + sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c))) + + def upload_file(self): + """Ask user for filenname and send its contents""" + sys.stderr.write('\n--- File to upload: ') + sys.stderr.flush() + with self.console: + filename = sys.stdin.readline().rstrip('\r\n') + if filename: + try: + with open(filename, 'rb') as f: + sys.stderr.write('--- Sending file {} ---\n'.format(filename)) + while True: + block = f.read(1024) + if not block: + break + self.serial.write(block) + # Wait for output buffer to drain. + self.serial.flush() + sys.stderr.write('.') # Progress indicator. + sys.stderr.write('\n--- File {} sent ---\n'.format(filename)) + except IOError as e: + sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e)) + + def change_filter(self): + """change the i/o transformations""" + sys.stderr.write('\n--- Available Filters:\n') + sys.stderr.write('\n'.join( + '--- {:<10} = {.__doc__}'.format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()))) + sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters))) + with self.console: + new_filters = sys.stdin.readline().lower().split() + if new_filters: + for f in new_filters: + if f not in TRANSFORMATIONS: + sys.stderr.write('--- unknown filter: {!r}\n'.format(f)) + break + else: + self.filters = new_filters + self.update_transformations() + sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters))) + + def change_encoding(self): + """change encoding on the serial port""" + sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding)) + with self.console: + new_encoding = sys.stdin.readline().strip() + if new_encoding: + try: + codecs.lookup(new_encoding) + except LookupError: + sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding)) + else: + self.set_rx_encoding(new_encoding) + self.set_tx_encoding(new_encoding) + sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding)) + sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding)) + + def change_baudrate(self): + """change the baudrate""" + sys.stderr.write('\n--- Baudrate: ') + sys.stderr.flush() + with self.console: + backup = self.serial.baudrate + try: + self.serial.baudrate = int(sys.stdin.readline().strip()) + except ValueError as e: + sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e)) + self.serial.baudrate = backup + else: + self.dump_port_settings() + + def change_port(self): + """Have a conversation with the user to change the serial port""" + with self.console: + try: + port = ask_for_port() + except KeyboardInterrupt: + port = None + if port and port != self.serial.port: + # reader thread needs to be shut down + self._stop_reader() + # save settings + settings = self.serial.getSettingsDict() + try: + new_serial = serial.serial_for_url(port, do_not_open=True) + # restore settings and open + new_serial.applySettingsDict(settings) + new_serial.rts = self.serial.rts + new_serial.dtr = self.serial.dtr + new_serial.open() + new_serial.break_condition = self.serial.break_condition + except Exception as e: + sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e)) + new_serial.close() + else: + self.serial.close() + self.serial = new_serial + sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port)) + # and restart the reader thread + self._start_reader() + + def suspend_port(self): + """\ + open port temporarily, allow reconnect, exit and port change to get + out of the loop + """ + # reader thread needs to be shut down + self._stop_reader() + self.serial.close() + sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port)) + do_change_port = False + while not self.serial.is_open: + sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format( + exit=key_description(self.exit_character))) + k = self.console.getkey() + if k == self.exit_character: + self.stop() # exit app + break + elif k in 'pP': + do_change_port = True + break + try: + self.serial.open() + except Exception as e: + sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e)) + if do_change_port: + self.change_port() + else: + # and restart the reader thread + self._start_reader() + sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port)) + + def get_help_text(self): + """return the help text""" + # help text, starts with blank line! + return """ +--- pySerial ({version}) - miniterm - help +--- +--- {exit:8} Exit program (alias {menu} Q) +--- {menu:8} Menu escape key, followed by: +--- Menu keys: +--- {menu:7} Send the menu character itself to remote +--- {exit:7} Send the exit character itself to remote +--- {info:7} Show info +--- {upload:7} Upload file (prompt will be shown) +--- {repr:7} encoding +--- {filter:7} edit filters +--- Toggles: +--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK +--- {echo:7} echo {eol:7} EOL +--- +--- Port settings ({menu} followed by the following): +--- p change port +--- 7 8 set data bits +--- N E O S M change parity (None, Even, Odd, Space, Mark) +--- 1 2 3 set stop bits (1, 2, 1.5) +--- b change baud rate +--- x X disable/enable software flow control +--- r R disable/enable hardware flow control +""".format(version=getattr(serial, 'VERSION', 'unknown version'), + exit=key_description(self.exit_character), + menu=key_description(self.menu_character), + rts=key_description('\x12'), + dtr=key_description('\x04'), + brk=key_description('\x02'), + echo=key_description('\x05'), + info=key_description('\x09'), + upload=key_description('\x15'), + repr=key_description('\x01'), + filter=key_description('\x06'), + eol=key_description('\x0c')) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# default args can be used to override when calling main() from an other script +# e.g to create a miniterm-my-device.py +def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None): + """Command line tool, entry point""" + + import argparse + + parser = argparse.ArgumentParser( + description='Miniterm - A simple terminal program for the serial port.') + + parser.add_argument( + 'port', + nargs='?', + help='serial port name ("-" to show port list)', + default=default_port) + + parser.add_argument( + 'baudrate', + nargs='?', + type=int, + help='set baud rate, default: %(default)s', + default=default_baudrate) + + group = parser.add_argument_group('port settings') + + group.add_argument( + '--parity', + choices=['N', 'E', 'O', 'S', 'M'], + type=lambda c: c.upper(), + help='set parity, one of {N E O S M}, default: N', + default='N') + + group.add_argument( + '--rtscts', + action='store_true', + help='enable RTS/CTS flow control (default off)', + default=False) + + group.add_argument( + '--xonxoff', + action='store_true', + help='enable software flow control (default off)', + default=False) + + group.add_argument( + '--rts', + type=int, + help='set initial RTS line state (possible values: 0, 1)', + default=default_rts) + + group.add_argument( + '--dtr', + type=int, + help='set initial DTR line state (possible values: 0, 1)', + default=default_dtr) + + group.add_argument( + '--non-exclusive', + dest='exclusive', + action='store_false', + help='disable locking for native ports', + default=True) + + group.add_argument( + '--ask', + action='store_true', + help='ask again for port when open fails', + default=False) + + group = parser.add_argument_group('data handling') + + group.add_argument( + '-e', '--echo', + action='store_true', + help='enable local echo (default off)', + default=False) + + group.add_argument( + '--encoding', + dest='serial_port_encoding', + metavar='CODEC', + help='set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s', + default='UTF-8') + + group.add_argument( + '-f', '--filter', + action='append', + metavar='NAME', + help='add text transformation', + default=[]) + + group.add_argument( + '--eol', + choices=['CR', 'LF', 'CRLF'], + type=lambda c: c.upper(), + help='end of line mode', + default='CRLF') + + group.add_argument( + '--raw', + action='store_true', + help='Do no apply any encodings/transformations', + default=False) + + group = parser.add_argument_group('hotkeys') + + group.add_argument( + '--exit-char', + type=int, + metavar='NUM', + help='Unicode of special character that is used to exit the application, default: %(default)s', + default=0x1d) # GS/CTRL+] + + group.add_argument( + '--menu-char', + type=int, + metavar='NUM', + help='Unicode code of special character that is used to control miniterm (menu), default: %(default)s', + default=0x14) # Menu: CTRL+T + + group = parser.add_argument_group('diagnostics') + + group.add_argument( + '-q', '--quiet', + action='store_true', + help='suppress non-error messages', + default=False) + + group.add_argument( + '--develop', + action='store_true', + help='show Python traceback on error', + default=False) + + args = parser.parse_args() + + if args.menu_char == args.exit_char: + parser.error('--exit-char can not be the same as --menu-char') + + if args.filter: + if 'help' in args.filter: + sys.stderr.write('Available filters:\n') + sys.stderr.write('\n'.join( + '{:<10} = {.__doc__}'.format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()))) + sys.stderr.write('\n') + sys.exit(1) + filters = args.filter + else: + filters = ['default'] + + while True: + # no port given on command line -> ask user now + if args.port is None or args.port == '-': + try: + args.port = ask_for_port() + except KeyboardInterrupt: + sys.stderr.write('\n') + parser.error('user aborted and port is not given') + else: + if not args.port: + parser.error('port is not given') + try: + serial_instance = serial.serial_for_url( + args.port, + args.baudrate, + parity=args.parity, + rtscts=args.rtscts, + xonxoff=args.xonxoff, + do_not_open=True) + + if not hasattr(serial_instance, 'cancel_read'): + # enable timeout for alive flag polling if cancel_read is not available + serial_instance.timeout = 1 + + if args.dtr is not None: + if not args.quiet: + sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive')) + serial_instance.dtr = args.dtr + if args.rts is not None: + if not args.quiet: + sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive')) + serial_instance.rts = args.rts + + if isinstance(serial_instance, serial.Serial): + serial_instance.exclusive = args.exclusive + + serial_instance.open() + except serial.SerialException as e: + sys.stderr.write('could not open port {!r}: {}\n'.format(args.port, e)) + if args.develop: + raise + if not args.ask: + sys.exit(1) + else: + args.port = '-' + else: + break + + miniterm = Miniterm( + serial_instance, + echo=args.echo, + eol=args.eol.lower(), + filters=filters) + miniterm.exit_character = unichr(args.exit_char) + miniterm.menu_character = unichr(args.menu_char) + miniterm.raw = args.raw + miniterm.set_rx_encoding(args.serial_port_encoding) + miniterm.set_tx_encoding(args.serial_port_encoding) + + if not args.quiet: + sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format( + p=miniterm.serial)) + sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format( + key_description(miniterm.exit_character), + key_description(miniterm.menu_character), + key_description(miniterm.menu_character), + key_description('\x08'))) + + miniterm.start() + try: + miniterm.join(True) + except KeyboardInterrupt: + pass + if not args.quiet: + sys.stderr.write('\n--- exit ---\n') + miniterm.join() + miniterm.close() + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + main() diff --git a/venv/Lib/site-packages/serial/urlhandler/__init__.py b/venv/Lib/site-packages/serial/urlhandler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/__init__.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74817b93ac5627d6ee0d403f6d87eb98210c5cae GIT binary patch literal 185 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_hwgrep.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_hwgrep.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef668e04cdc72c8db87557f360ceb7446b8ab9ed GIT binary patch literal 1869 zcmZux&5ztP6t|u4?96OcRqcW}gcd3WmDzGZuqvohw;-Wvk=T~fXi$_HduKA6ak92& zKZfMOmKz7ooMvwDKlCs0mDAoh?}_K6g+(Hf9e=#|{rr3H_tI`RMDXl#HhSJc=ub1b z`uH$;2u+sIQABZ$<~YXKj7d&n(tNF$EPZBU^c?YZfw6moZLrWA!theJq$fgeE_Pj-eP+1mJLNF?;6B+!;8b)=LDATxwJ2C5nCO8W-B#XCh1U3^=g{g|vltnyM*)faS{mA%@z&;5TjkSDViImGc zRjkFv(>aSq?4S@VlBy6HpG29u45lX&!OETO?Y+LQ!%Sves+5nJ4o9hEQW--X!kSR> zBvVuE%7zhPIMuefmG+WEGL-{ z4V0>4T;xf6lRjvH;%Z)Zr0|WR!|$y=z#)UFA2YO4u~yT-L$*(*p~7&(=_i+

    OTEfHM&kR*g}`~#Uy~uJz1rt~ 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_rfc2217.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_rfc2217.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70f870ead92690aedf43e785b7ad1d5d48053665 GIT binary patch literal 288 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 K(VrntEB^ytIr$#| literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_spy.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_spy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b5b1ec909e49d592cee7d901d6c8cb86238596f GIT binary patch literal 9146 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/serial/urlhandler/protocol_alt.py b/venv/Lib/site-packages/serial/urlhandler/protocol_alt.py new file mode 100644 index 0000000..2e666ca --- /dev/null +++ b/venv/Lib/site-packages/serial/urlhandler/protocol_alt.py @@ -0,0 +1,57 @@ +#! python +# +# This module implements a special URL handler that allows selecting an +# alternate implementation provided by some backends. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# URL format: alt://port[?option[=value][&option[=value]]] +# options: +# - class=X used class named X instead of Serial +# +# example: +# use poll based implementation on Posix (Linux): +# python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial + +from __future__ import absolute_import + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +import serial + + +def serial_class_for_url(url): + """extract host and port from an URL string""" + parts = urlparse.urlsplit(url) + if parts.scheme != 'alt': + raise serial.SerialException( + 'expected a string in the form "alt://port[?option[=value][&option[=value]]]": ' + 'not starting with alt:// ({!r})'.format(parts.scheme)) + class_name = 'Serial' + try: + for option, values in urlparse.parse_qs(parts.query, True).items(): + if option == 'class': + class_name = values[0] + else: + raise ValueError('unknown option: {!r}'.format(option)) + except ValueError as e: + raise serial.SerialException( + 'expected a string in the form ' + '"alt://port[?option[=value][&option[=value]]]": {!r}'.format(e)) + if not hasattr(serial, class_name): + raise ValueError('unknown class: {!r}'.format(class_name)) + cls = getattr(serial, class_name) + if not issubclass(cls, serial.Serial): + raise ValueError('class {!r} is not an instance of Serial'.format(class_name)) + return (''.join([parts.netloc, parts.path]), cls) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + s = serial.serial_for_url('alt:///dev/ttyS0?class=PosixPollSerial') + print(s) diff --git a/venv/Lib/site-packages/serial/urlhandler/protocol_cp2110.py b/venv/Lib/site-packages/serial/urlhandler/protocol_cp2110.py new file mode 100644 index 0000000..44ad4eb --- /dev/null +++ b/venv/Lib/site-packages/serial/urlhandler/protocol_cp2110.py @@ -0,0 +1,258 @@ +#! python +# +# Backend for Silicon Labs CP2110/4 HID-to-UART devices. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2001-2015 Chris Liechti +# (C) 2019 Google LLC +# +# SPDX-License-Identifier: BSD-3-Clause + +# This backend implements support for HID-to-UART devices manufactured +# by Silicon Labs and marketed as CP2110 and CP2114. The +# implementation is (mostly) OS-independent and in userland. It relies +# on cython-hidapi (https://github.com/trezor/cython-hidapi). + +# The HID-to-UART protocol implemented by CP2110/4 is described in the +# AN434 document from Silicon Labs: +# https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf + +# TODO items: + +# - rtscts support is configured for hardware flow control, but the +# signaling is missing (AN434 suggests this is done through GPIO). +# - Cancelling reads and writes is not supported. +# - Baudrate validation is not implemented, as it depends on model and configuration. + +import struct +import threading + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +try: + import Queue +except ImportError: + import queue as Queue + +import hid # hidapi + +import serial +from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout + + +# Report IDs and related constant +_REPORT_GETSET_UART_ENABLE = 0x41 +_DISABLE_UART = 0x00 +_ENABLE_UART = 0x01 + +_REPORT_SET_PURGE_FIFOS = 0x43 +_PURGE_TX_FIFO = 0x01 +_PURGE_RX_FIFO = 0x02 + +_REPORT_GETSET_UART_CONFIG = 0x50 + +_REPORT_SET_TRANSMIT_LINE_BREAK = 0x51 +_REPORT_SET_STOP_LINE_BREAK = 0x52 + + +class Serial(SerialBase): + # This is not quite correct. AN343 specifies that the minimum + # baudrate is different between CP2110 and CP2114, and it's halved + # when using non-8-bit symbols. + BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200, + 38400, 57600, 115200, 230400, 460800, 500000, 576000, + 921600, 1000000) + + def __init__(self, *args, **kwargs): + self._hid_handle = None + self._read_buffer = None + self._thread = None + super(Serial, self).__init__(*args, **kwargs) + + def open(self): + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self.is_open: + raise SerialException("Port is already open.") + + self._read_buffer = Queue.Queue() + + self._hid_handle = hid.device() + try: + portpath = self.from_url(self.portstr) + self._hid_handle.open_path(portpath) + except OSError as msg: + raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) + + try: + self._reconfigure_port() + except: + try: + self._hid_handle.close() + except: + pass + self._hid_handle = None + raise + else: + self.is_open = True + self._thread = threading.Thread(target=self._hid_read_loop) + self._thread.setDaemon(True) + self._thread.setName('pySerial CP2110 reader thread for {}'.format(self._port)) + self._thread.start() + + def from_url(self, url): + parts = urlparse.urlsplit(url) + if parts.scheme != "cp2110": + raise SerialException( + 'expected a string in the forms ' + '"cp2110:///dev/hidraw9" or "cp2110://0001:0023:00": ' + 'not starting with cp2110:// {{!r}}'.format(parts.scheme)) + if parts.netloc: # cp2100://BUS:DEVICE:ENDPOINT, for libusb + return parts.netloc.encode('utf-8') + return parts.path.encode('utf-8') + + def close(self): + self.is_open = False + if self._thread: + self._thread.join(1) # read timeout is 0.1 + self._thread = None + self._hid_handle.close() + self._hid_handle = None + + def _reconfigure_port(self): + parity_value = None + if self._parity == serial.PARITY_NONE: + parity_value = 0x00 + elif self._parity == serial.PARITY_ODD: + parity_value = 0x01 + elif self._parity == serial.PARITY_EVEN: + parity_value = 0x02 + elif self._parity == serial.PARITY_MARK: + parity_value = 0x03 + elif self._parity == serial.PARITY_SPACE: + parity_value = 0x04 + else: + raise ValueError('Invalid parity: {!r}'.format(self._parity)) + + if self.rtscts: + flow_control_value = 0x01 + else: + flow_control_value = 0x00 + + data_bits_value = None + if self._bytesize == 5: + data_bits_value = 0x00 + elif self._bytesize == 6: + data_bits_value = 0x01 + elif self._bytesize == 7: + data_bits_value = 0x02 + elif self._bytesize == 8: + data_bits_value = 0x03 + else: + raise ValueError('Invalid char len: {!r}'.format(self._bytesize)) + + stop_bits_value = None + if self._stopbits == serial.STOPBITS_ONE: + stop_bits_value = 0x00 + elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE: + stop_bits_value = 0x01 + elif self._stopbits == serial.STOPBITS_TWO: + stop_bits_value = 0x01 + else: + raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits)) + + configuration_report = struct.pack( + '>BLBBBB', + _REPORT_GETSET_UART_CONFIG, + self._baudrate, + parity_value, + flow_control_value, + data_bits_value, + stop_bits_value) + + self._hid_handle.send_feature_report(configuration_report) + + self._hid_handle.send_feature_report( + bytes((_REPORT_GETSET_UART_ENABLE, _ENABLE_UART))) + self._update_break_state() + + @property + def in_waiting(self): + return self._read_buffer.qsize() + + def reset_input_buffer(self): + if not self.is_open: + raise PortNotOpenError() + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_RX_FIFO))) + # empty read buffer + while self._read_buffer.qsize(): + self._read_buffer.get(False) + + def reset_output_buffer(self): + if not self.is_open: + raise PortNotOpenError() + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_TX_FIFO))) + + def _update_break_state(self): + if not self._hid_handle: + raise PortNotOpenError() + + if self._break_state: + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_TRANSMIT_LINE_BREAK, 0))) + else: + # Note that while AN434 states "There are no data bytes in + # the payload other than the Report ID", either hidapi or + # Linux does not seem to send the report otherwise. + self._hid_handle.send_feature_report( + bytes((_REPORT_SET_STOP_LINE_BREAK, 0))) + + def read(self, size=1): + if not self.is_open: + raise PortNotOpenError() + + data = bytearray() + try: + timeout = Timeout(self._timeout) + while len(data) < size: + if self._thread is None: + raise SerialException('connection failed (reader thread died)') + buf = self._read_buffer.get(True, timeout.time_left()) + if buf is None: + return bytes(data) + data += buf + if timeout.expired(): + break + except Queue.Empty: # -> timeout + pass + return bytes(data) + + def write(self, data): + if not self.is_open: + raise PortNotOpenError() + data = to_bytes(data) + tx_len = len(data) + while tx_len > 0: + to_be_sent = min(tx_len, 0x3F) + report = to_bytes([to_be_sent]) + data[:to_be_sent] + self._hid_handle.write(report) + + data = data[to_be_sent:] + tx_len = len(data) + + def _hid_read_loop(self): + try: + while self.is_open: + data = self._hid_handle.read(64, timeout_ms=100) + if not data: + continue + data_len = data.pop(0) + assert data_len == len(data) + self._read_buffer.put(bytearray(data)) + finally: + self._thread = None diff --git a/venv/Lib/site-packages/serial/urlhandler/protocol_hwgrep.py b/venv/Lib/site-packages/serial/urlhandler/protocol_hwgrep.py new file mode 100644 index 0000000..1a288c9 --- /dev/null +++ b/venv/Lib/site-packages/serial/urlhandler/protocol_hwgrep.py @@ -0,0 +1,91 @@ +#! python +# +# This module implements a special URL handler that uses the port listing to +# find ports by searching the string descriptions. +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C) 2011-2015 Chris Liechti +# +# SPDX-License-Identifier: BSD-3-Clause +# +# URL format: hwgrep://&