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 0000000..00974ed
Binary files /dev/null and b/venv/Lib/site-packages/__pycache__/docopt.cpython-39.pyc differ
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 0000000..5b83788
Binary files /dev/null and b/venv/Lib/site-packages/__pycache__/uflash.cpython-39.pyc differ
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 0000000..466289d
Binary files /dev/null and b/venv/Lib/site-packages/ampy/__pycache__/__init__.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/ampy/__pycache__/cli.cpython-39.pyc b/venv/Lib/site-packages/ampy/__pycache__/cli.cpython-39.pyc
new file mode 100644
index 0000000..0f25a3d
Binary files /dev/null and b/venv/Lib/site-packages/ampy/__pycache__/cli.cpython-39.pyc differ
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 0000000..4ba2aaa
Binary files /dev/null and b/venv/Lib/site-packages/ampy/__pycache__/files.cpython-39.pyc differ
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 0000000..4081459
Binary files /dev/null and b/venv/Lib/site-packages/ampy/__pycache__/pyboard.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/ampy/cli.py b/venv/Lib/site-packages/ampy/cli.py
new file mode 100644
index 0000000..d974d9c
--- /dev/null
+++ b/venv/Lib/site-packages/ampy/cli.py
@@ -0,0 +1,429 @@
+# Adafruit MicroPython Tool - Command Line Interface
+# Author: Tony DiCola
+# 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.
+from __future__ import print_function
+import os
+import platform
+import posixpath
+import re
+import serial.serialutil
+
+import click
+import dotenv
+
+# Load AMPY_PORT et al from .ampy file
+# Performed here because we need to beat click's decorators.
+config = dotenv.find_dotenv(filename=".ampy", usecwd=True)
+if config:
+ dotenv.load_dotenv(dotenv_path=config)
+
+import ampy.files as files
+import ampy.pyboard as pyboard
+
+
+_board = None
+
+
+def windows_full_port_name(portname):
+ # Helper function to generate proper Windows COM port paths. Apparently
+ # Windows requires COM ports above 9 to have a special path, where ports below
+ # 9 are just referred to by COM1, COM2, etc. (wacky!) See this post for
+ # more info and where this code came from:
+ # http://eli.thegreenplace.net/2009/07/31/listing-all-serial-ports-on-windows-with-python/
+ m = re.match("^COM(\d+)$", portname)
+ if m and int(m.group(1)) < 10:
+ return portname
+ else:
+ return "\\\\.\\{0}".format(portname)
+
+
+@click.group()
+@click.option(
+ "--port",
+ "-p",
+ envvar="AMPY_PORT",
+ required=True,
+ type=click.STRING,
+ help="Name of serial port for connected board. Can optionally specify with AMPY_PORT environment variable.",
+ metavar="PORT",
+)
+@click.option(
+ "--baud",
+ "-b",
+ envvar="AMPY_BAUD",
+ default=115200,
+ type=click.INT,
+ help="Baud rate for the serial connection (default 115200). Can optionally specify with AMPY_BAUD environment variable.",
+ metavar="BAUD",
+)
+@click.option(
+ "--delay",
+ "-d",
+ envvar="AMPY_DELAY",
+ default=0,
+ type=click.FLOAT,
+ help="Delay in seconds before entering RAW MODE (default 0). Can optionally specify with AMPY_DELAY environment variable.",
+ metavar="DELAY",
+)
+@click.version_option()
+def cli(port, baud, delay):
+ """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.
+ """
+ global _board
+ # On Windows fix the COM port path name for ports above 9 (see comment in
+ # windows_full_port_name function).
+ if platform.system() == "Windows":
+ port = windows_full_port_name(port)
+ _board = pyboard.Pyboard(port, baudrate=baud, rawdelay=delay)
+
+
+@cli.command()
+@click.argument("remote_file")
+@click.argument("local_file", type=click.File("wb"), required=False)
+def get(remote_file, local_file):
+ """
+ Retrieve a file from the board.
+
+ Get will download a file from the board and print its contents or save it
+ locally. You must pass at least one argument which is the path to the file
+ to download from the board. If you don't specify a second argument then
+ the file contents will be printed to standard output. However if you pass
+ a file name as the second argument then the contents of the downloaded file
+ will be saved to that file (overwriting anything inside it!).
+
+ For example to retrieve the boot.py and print it out run:
+
+ ampy --port /board/serial/port get boot.py
+
+ Or to get main.py and save it as main.py locally run:
+
+ ampy --port /board/serial/port get main.py main.py
+ """
+ # Get the file contents.
+ board_files = files.Files(_board)
+ contents = board_files.get(remote_file)
+ # Print the file out if no local file was provided, otherwise save it.
+ if local_file is None:
+ print(contents.decode("utf-8"))
+ else:
+ local_file.write(contents)
+
+
+@cli.command()
+@click.option(
+ "--exists-okay", is_flag=True, help="Ignore if the directory already exists."
+)
+@click.argument("directory")
+def mkdir(directory, exists_okay):
+ """
+ Create a directory on the board.
+
+ Mkdir will create the specified directory on the board. One argument is
+ required, the full path of the directory to create.
+
+ Note that you cannot recursively create a hierarchy of directories with one
+ mkdir command, instead you must create each parent directory with separate
+ mkdir command calls.
+
+ For example to make a directory under the root called 'code':
+
+ ampy --port /board/serial/port mkdir /code
+ """
+ # Run the mkdir command.
+ board_files = files.Files(_board)
+ board_files.mkdir(directory, exists_okay=exists_okay)
+
+
+@cli.command()
+@click.argument("directory", default="/")
+@click.option(
+ "--long_format",
+ "-l",
+ is_flag=True,
+ help="Print long format info including size of files. Note the size of directories is not supported and will show 0 values.",
+)
+@click.option(
+ "--recursive",
+ "-r",
+ is_flag=True,
+ help="recursively list all files and (empty) directories.",
+)
+def ls(directory, long_format, recursive):
+ """List contents of a directory on the board.
+
+ Can pass an optional argument which is the path to the directory. The
+ default is to list the contents of the root, /, path.
+
+ For example to list the contents of the root run:
+
+ ampy --port /board/serial/port ls
+
+ Or to list the contents of the /foo/bar directory on the board run:
+
+ ampy --port /board/serial/port ls /foo/bar
+
+ Add the -l or --long_format flag to print the size of files (however note
+ MicroPython does not calculate the size of folders and will show 0 bytes):
+
+ ampy --port /board/serial/port ls -l /foo/bar
+ """
+ # List each file/directory on a separate line.
+ board_files = files.Files(_board)
+ for f in board_files.ls(directory, long_format=long_format, recursive=recursive):
+ print(f)
+
+
+@cli.command()
+@click.argument("local", type=click.Path(exists=True))
+@click.argument("remote", required=False)
+def put(local, remote):
+ """Put a file or folder and its contents on the board.
+
+ Put will upload a local file or folder to the board. If the file already
+ exists on the board it will be overwritten with no warning! You must pass
+ at least one argument which is the path to the local file/folder to
+ upload. If the item to upload is a folder then it will be copied to the
+ board recursively with its entire child structure. You can pass a second
+ optional argument which is the path and name of the file/folder to put to
+ on the connected board.
+
+ For example to upload a main.py from the current directory to the board's
+ root run:
+
+ ampy --port /board/serial/port put main.py
+
+ Or to upload a board_boot.py from a ./foo subdirectory and save it as boot.py
+ in the board's root run:
+
+ ampy --port /board/serial/port put ./foo/board_boot.py boot.py
+
+ To upload a local folder adafruit_library and all of its child files/folders
+ as an item under the board's root run:
+
+ ampy --port /board/serial/port put adafruit_library
+
+ Or to put a local folder adafruit_library on the board under the path
+ /lib/adafruit_library on the board run:
+
+ ampy --port /board/serial/port put adafruit_library /lib/adafruit_library
+ """
+ # Use the local filename if no remote filename is provided.
+ if remote is None:
+ remote = os.path.basename(os.path.abspath(local))
+ # Check if path is a folder and do recursive copy of everything inside it.
+ # Otherwise it's a file and should simply be copied over.
+ if os.path.isdir(local):
+ # Directory copy, create the directory and walk all children to copy
+ # over the files.
+ board_files = files.Files(_board)
+ for parent, child_dirs, child_files in os.walk(local):
+ # Create board filesystem absolute path to parent directory.
+ remote_parent = posixpath.normpath(
+ posixpath.join(remote, os.path.relpath(parent, local))
+ )
+ try:
+ # Create remote parent directory.
+ board_files.mkdir(remote_parent)
+ # Loop through all the files and put them on the board too.
+ for filename in child_files:
+ with open(os.path.join(parent, filename), "rb") as infile:
+ remote_filename = posixpath.join(remote_parent, filename)
+ board_files.put(remote_filename, infile.read())
+ except files.DirectoryExistsError:
+ # Ignore errors for directories that already exist.
+ pass
+
+ else:
+ # File copy, open the file and copy its contents to the board.
+ # Put the file on the board.
+ with open(local, "rb") as infile:
+ board_files = files.Files(_board)
+ board_files.put(remote, infile.read())
+
+
+@cli.command()
+@click.argument("remote_file")
+def rm(remote_file):
+ """Remove a file from the board.
+
+ Remove the specified file from the board's filesystem. Must specify one
+ argument which is the path to the file to delete. Note that this can't
+ delete directories which have files inside them, but can delete empty
+ directories.
+
+ For example to delete main.py from the root of a board run:
+
+ ampy --port /board/serial/port rm main.py
+ """
+ # Delete the provided file/directory on the board.
+ board_files = files.Files(_board)
+ board_files.rm(remote_file)
+
+
+@cli.command()
+@click.option(
+ "--missing-okay", is_flag=True, help="Ignore if the directory does not exist."
+)
+@click.argument("remote_folder")
+def rmdir(remote_folder, missing_okay):
+ """Forcefully remove a folder and all its children from the board.
+
+ Remove the specified folder from the board's filesystem. Must specify one
+ argument which is the path to the folder to delete. This will delete the
+ directory and ALL of its children recursively, use with caution!
+
+ For example to delete everything under /adafruit_library from the root of a
+ board run:
+
+ ampy --port /board/serial/port rmdir adafruit_library
+ """
+ # Delete the provided file/directory on the board.
+ board_files = files.Files(_board)
+ board_files.rmdir(remote_folder, missing_okay=missing_okay)
+
+
+@cli.command()
+@click.argument("local_file")
+@click.option(
+ "--no-output",
+ "-n",
+ is_flag=True,
+ help="Run the code without waiting for it to finish and print output. Use this when running code with main loops that never return.",
+)
+def run(local_file, no_output):
+ """Run a script and print its output.
+
+ Run will send the specified file to the board and execute it immediately.
+ Any output from the board will be printed to the console (note that this is
+ not a 'shell' and you can't send input to the program).
+
+ Note that if your code has a main or infinite loop you should add the --no-output
+ option. This will run the script and immediately exit without waiting for
+ the script to finish and print output.
+
+ For example to run a test.py script and print any output after it finishes:
+
+ ampy --port /board/serial/port run test.py
+
+ Or to run test.py and not wait for it to finish:
+
+ ampy --port /board/serial/port run --no-output test.py
+ """
+ # Run the provided file and print its output.
+ board_files = files.Files(_board)
+ try:
+ output = board_files.run(local_file, not no_output)
+ if output is not None:
+ print(output.decode("utf-8"), end="")
+ except IOError:
+ click.echo(
+ "Failed to find or read input file: {0}".format(local_file), err=True
+ )
+
+
+@cli.command()
+@click.option(
+ "--bootloader", "mode", flag_value="BOOTLOADER", help="Reboot into the bootloader"
+)
+@click.option(
+ "--hard",
+ "mode",
+ flag_value="NORMAL",
+ help="Perform a hard reboot, including running init.py",
+)
+@click.option(
+ "--repl",
+ "mode",
+ flag_value="SOFT",
+ default=True,
+ help="Perform a soft reboot, entering the REPL [default]",
+)
+@click.option(
+ "--safe",
+ "mode",
+ flag_value="SAFE_MODE",
+ help="Perform a safe-mode reboot. User code will not be run and the filesystem will be writeable over USB",
+)
+def reset(mode):
+ """Perform soft reset/reboot of the board.
+
+ Will connect to the board and perform a reset. Depending on the board
+ and firmware, several different types of reset may be supported.
+
+ ampy --port /board/serial/port reset
+ """
+ _board.enter_raw_repl()
+ if mode == "SOFT":
+ _board.exit_raw_repl()
+ return
+
+ _board.exec_(
+ """if 1:
+ def on_next_reset(x):
+ try:
+ import microcontroller
+ except:
+ if x == 'NORMAL': return ''
+ return 'Reset mode only supported on CircuitPython'
+ try:
+ microcontroller.on_next_reset(getattr(microcontroller.RunMode, x))
+ except ValueError as e:
+ return str(e)
+ return ''
+ def reset():
+ try:
+ import microcontroller
+ except:
+ import machine as microcontroller
+ microcontroller.reset()
+ """
+ )
+ r = _board.eval("on_next_reset({})".format(repr(mode)))
+ print("here we are", repr(r))
+ if r:
+ click.echo(r, err=True)
+ return
+
+ try:
+ _board.exec_("reset()")
+ except serial.serialutil.SerialException as e:
+ # An error is expected to occur, as the board should disconnect from
+ # serial when restarted via microcontroller.reset()
+ pass
+
+
+if __name__ == "__main__":
+ try:
+ cli()
+ finally:
+ # Try to ensure the board serial connection is always gracefully closed.
+ if _board is not None:
+ try:
+ _board.close()
+ except:
+ # Swallow errors when attempting to close as it's just a best effort
+ # and shouldn't cause a new error or problem if the connection can't
+ # be closed.
+ pass
diff --git a/venv/Lib/site-packages/ampy/files.py b/venv/Lib/site-packages/ampy/files.py
new file mode 100644
index 0000000..d7f3d00
--- /dev/null
+++ b/venv/Lib/site-packages/ampy/files.py
@@ -0,0 +1,310 @@
+# Adafruit MicroPython Tool - File Operations
+# Author: Tony DiCola
+# 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.
+import ast
+import textwrap
+
+from ampy.pyboard import PyboardError
+
+
+BUFFER_SIZE = 32 # Amount of data to read or write to the serial port at a time.
+# This is kept small because small chips and USB to serial
+# bridges usually have very small buffers.
+
+
+class DirectoryExistsError(Exception):
+ pass
+
+
+class Files(object):
+ """Class to interact with a MicroPython board files over a serial connection.
+ Provides functions for listing, uploading, and downloading files from the
+ board's filesystem.
+ """
+
+ def __init__(self, pyboard):
+ """Initialize the MicroPython board files class using the provided pyboard
+ instance. In most cases you should create a Pyboard instance (from
+ pyboard.py) which connects to a board over a serial connection and pass
+ it in, but you can pass in other objects for testing, etc.
+ """
+ self._pyboard = pyboard
+
+ def get(self, filename):
+ """Retrieve the contents of the specified file and return its contents
+ as a byte string.
+ """
+ # Open the file and read it a few bytes at a time and print out the
+ # raw bytes. Be careful not to overload the UART buffer so only write
+ # a few bytes at a time, and don't use print since it adds newlines and
+ # expects string data.
+ command = """
+ import sys
+ with open('{0}', 'rb') as infile:
+ while True:
+ result = infile.read({1})
+ if result == b'':
+ break
+ len = sys.stdout.write(result)
+ """.format(
+ filename, BUFFER_SIZE
+ )
+ self._pyboard.enter_raw_repl()
+ try:
+ out = self._pyboard.exec_(textwrap.dedent(command))
+ except PyboardError as ex:
+ # Check if this is an OSError #2, i.e. file doesn't exist and
+ # rethrow it as something more descriptive.
+ if ex.args[2].decode("utf-8").find("OSError: [Errno 2] ENOENT") != -1:
+ raise RuntimeError("No such file: {0}".format(filename))
+ else:
+ raise ex
+ self._pyboard.exit_raw_repl()
+ return out
+
+ def ls(self, directory="/", long_format=True, recursive=False):
+ """List the contents of the specified directory (or root if none is
+ specified). Returns a list of strings with the names of files in the
+ specified directory. If long_format is True then a list of 2-tuples
+ with the name and size (in bytes) of the item is returned. Note that
+ it appears the size of directories is not supported by MicroPython and
+ will always return 0 (i.e. no recursive size computation).
+ """
+
+ # Disabling for now, see https://github.com/adafruit/ampy/issues/55.
+ # # Make sure directory ends in a slash.
+ # if not directory.endswith("/"):
+ # directory += "/"
+
+ # Make sure directory starts with slash, for consistency.
+ if not directory.startswith("/"):
+ directory = "/" + directory
+
+ command = """\
+ try:
+ import os
+ except ImportError:
+ import uos as os\n"""
+
+ if recursive:
+ command += """\
+ def listdir(directory):
+ result = set()
+
+ def _listdir(dir_or_file):
+ try:
+ # if its a directory, then it should provide some children.
+ children = os.listdir(dir_or_file)
+ except OSError:
+ # probably a file. run stat() to confirm.
+ os.stat(dir_or_file)
+ result.add(dir_or_file)
+ else:
+ # probably a directory, add to result if empty.
+ if children:
+ # queue the children to be dealt with in next iteration.
+ for child in children:
+ # create the full path.
+ if dir_or_file == '/':
+ next = dir_or_file + child
+ else:
+ next = dir_or_file + '/' + child
+
+ _listdir(next)
+ else:
+ result.add(dir_or_file)
+
+ _listdir(directory)
+ return sorted(result)\n"""
+ else:
+ command += """\
+ def listdir(directory):
+ if directory == '/':
+ return sorted([directory + f for f in os.listdir(directory)])
+ else:
+ return sorted([directory + '/' + f for f in os.listdir(directory)])\n"""
+
+ # Execute os.listdir() command on the board.
+ if long_format:
+ command += """
+ r = []
+ for f in listdir('{0}'):
+ size = os.stat(f)[6]
+ r.append('{{0}} - {{1}} bytes'.format(f, size))
+ print(r)
+ """.format(
+ directory
+ )
+ else:
+ command += """
+ print(listdir('{0}'))
+ """.format(
+ directory
+ )
+ self._pyboard.enter_raw_repl()
+ try:
+ out = self._pyboard.exec_(textwrap.dedent(command))
+ except PyboardError as ex:
+ # Check if this is an OSError #2, i.e. directory doesn't exist and
+ # rethrow it as something more descriptive.
+ if ex.args[2].decode("utf-8").find("OSError: [Errno 2] ENOENT") != -1:
+ raise RuntimeError("No such directory: {0}".format(directory))
+ else:
+ raise ex
+ self._pyboard.exit_raw_repl()
+ # Parse the result list and return it.
+ return ast.literal_eval(out.decode("utf-8"))
+
+ def mkdir(self, directory, exists_okay=False):
+ """Create the specified directory. Note this cannot create a recursive
+ hierarchy of directories, instead each one should be created separately.
+ """
+ # Execute os.mkdir command on the board.
+ command = """
+ try:
+ import os
+ except ImportError:
+ import uos as os
+ os.mkdir('{0}')
+ """.format(
+ directory
+ )
+ self._pyboard.enter_raw_repl()
+ try:
+ out = self._pyboard.exec_(textwrap.dedent(command))
+ except PyboardError as ex:
+ # Check if this is an OSError #17, i.e. directory already exists.
+ if ex.args[2].decode("utf-8").find("OSError: [Errno 17] EEXIST") != -1:
+ if not exists_okay:
+ raise DirectoryExistsError(
+ "Directory already exists: {0}".format(directory)
+ )
+ else:
+ raise ex
+ self._pyboard.exit_raw_repl()
+
+ def put(self, filename, data):
+ """Create or update the specified file with the provided data.
+ """
+ # Open the file for writing on the board and write chunks of data.
+ self._pyboard.enter_raw_repl()
+ self._pyboard.exec_("f = open('{0}', 'wb')".format(filename))
+ size = len(data)
+ # Loop through and write a buffer size chunk of data at a time.
+ for i in range(0, size, BUFFER_SIZE):
+ chunk_size = min(BUFFER_SIZE, size - i)
+ chunk = repr(data[i : i + chunk_size])
+ # Make sure to send explicit byte strings (handles python 2 compatibility).
+ if not chunk.startswith("b"):
+ chunk = "b" + chunk
+ self._pyboard.exec_("f.write({0})".format(chunk))
+ self._pyboard.exec_("f.close()")
+ self._pyboard.exit_raw_repl()
+
+ def rm(self, filename):
+ """Remove the specified file or directory."""
+ command = """
+ try:
+ import os
+ except ImportError:
+ import uos as os
+ os.remove('{0}')
+ """.format(
+ filename
+ )
+ self._pyboard.enter_raw_repl()
+ try:
+ out = self._pyboard.exec_(textwrap.dedent(command))
+ except PyboardError as ex:
+ message = ex.args[2].decode("utf-8")
+ # Check if this is an OSError #2, i.e. file/directory doesn't exist
+ # and rethrow it as something more descriptive.
+ if message.find("OSError: [Errno 2] ENOENT") != -1:
+ raise RuntimeError("No such file/directory: {0}".format(filename))
+ # Check for OSError #13, the directory isn't empty.
+ if message.find("OSError: [Errno 13] EACCES") != -1:
+ raise RuntimeError("Directory is not empty: {0}".format(filename))
+ else:
+ raise ex
+ self._pyboard.exit_raw_repl()
+
+ def rmdir(self, directory, missing_okay=False):
+ """Forcefully remove the specified directory and all its children."""
+ # Build a script to walk an entire directory structure and delete every
+ # file and subfolder. This is tricky because MicroPython has no os.walk
+ # or similar function to walk folders, so this code does it manually
+ # with recursion and changing directories. For each directory it lists
+ # the files and deletes everything it can, i.e. all the files. Then
+ # it lists the files again and assumes they are directories (since they
+ # couldn't be deleted in the first pass) and recursively clears those
+ # subdirectories. Finally when finished clearing all the children the
+ # parent directory is deleted.
+ command = """
+ try:
+ import os
+ except ImportError:
+ import uos as os
+ def rmdir(directory):
+ os.chdir(directory)
+ for f in os.listdir():
+ try:
+ os.remove(f)
+ except OSError:
+ pass
+ for f in os.listdir():
+ rmdir(f)
+ os.chdir('..')
+ os.rmdir(directory)
+ rmdir('{0}')
+ """.format(
+ directory
+ )
+ self._pyboard.enter_raw_repl()
+ try:
+ out = self._pyboard.exec_(textwrap.dedent(command))
+ except PyboardError as ex:
+ message = ex.args[2].decode("utf-8")
+ # Check if this is an OSError #2, i.e. directory doesn't exist
+ # and rethrow it as something more descriptive.
+ if message.find("OSError: [Errno 2] ENOENT") != -1:
+ if not missing_okay:
+ raise RuntimeError("No such directory: {0}".format(directory))
+ else:
+ raise ex
+ self._pyboard.exit_raw_repl()
+
+ def run(self, filename, wait_output=True):
+ """Run the provided script and return its output. If wait_output is True
+ (default) then wait for the script to finish and then print its output,
+ otherwise just run the script and don't wait for any output.
+ """
+ self._pyboard.enter_raw_repl()
+ out = None
+ if wait_output:
+ # Run the file and wait for output to return.
+ out = self._pyboard.execfile(filename)
+ else:
+ # Read the file and run it using lower level pyboard functions that
+ # won't wait for it to finish or return output.
+ with open(filename, "rb") as infile:
+ self._pyboard.exec_raw_no_follow(infile.read())
+ self._pyboard.exit_raw_repl()
+ return out
diff --git a/venv/Lib/site-packages/ampy/pyboard.py b/venv/Lib/site-packages/ampy/pyboard.py
new file mode 100644
index 0000000..2acc75f
--- /dev/null
+++ b/venv/Lib/site-packages/ampy/pyboard.py
@@ -0,0 +1,343 @@
+#!/usr/bin/env python
+
+"""
+pyboard interface
+
+This module provides the Pyboard class, used to communicate with and
+control the pyboard over a serial USB connection.
+
+Example usage:
+
+ import pyboard
+ pyb = pyboard.Pyboard('/dev/ttyACM0')
+
+Or:
+
+ pyb = pyboard.Pyboard('192.168.1.1')
+
+Then:
+
+ pyb.enter_raw_repl()
+ pyb.exec('pyb.LED(1).on()')
+ pyb.exit_raw_repl()
+
+Note: if using Python2 then pyb.exec must be written as pyb.exec_.
+To run a script from the local machine on the board and print out the results:
+
+ import pyboard
+ pyboard.execfile('test.py', device='/dev/ttyACM0')
+
+This script can also be run directly. To execute a local script, use:
+
+ ./pyboard.py test.py
+
+Or:
+
+ python pyboard.py test.py
+
+"""
+
+import sys
+import time
+
+_rawdelay = None
+
+try:
+ stdout = sys.stdout.buffer
+except AttributeError:
+ # Python2 doesn't have buffer attr
+ stdout = sys.stdout
+
+def stdout_write_bytes(b):
+ b = b.replace(b"\x04", b"")
+ stdout.write(b)
+ stdout.flush()
+
+class PyboardError(BaseException):
+ pass
+
+class TelnetToSerial:
+ def __init__(self, ip, user, password, read_timeout=None):
+ import telnetlib
+ self.tn = telnetlib.Telnet(ip, timeout=15)
+ self.read_timeout = read_timeout
+ if b'Login as:' in self.tn.read_until(b'Login as:', timeout=read_timeout):
+ self.tn.write(bytes(user, 'ascii') + b"\r\n")
+
+ if b'Password:' in self.tn.read_until(b'Password:', timeout=read_timeout):
+ # needed because of internal implementation details of the telnet server
+ time.sleep(0.2)
+ self.tn.write(bytes(password, 'ascii') + b"\r\n")
+
+ if b'for more information.' in self.tn.read_until(b'Type "help()" for more information.', timeout=read_timeout):
+ # login succesful
+ from collections import deque
+ self.fifo = deque()
+ return
+
+ raise PyboardError('Failed to establish a telnet connection with the board')
+
+ def __del__(self):
+ self.close()
+
+ def close(self):
+ try:
+ self.tn.close()
+ except:
+ # the telnet object might not exist yet, so ignore this one
+ pass
+
+ def read(self, size=1):
+ while len(self.fifo) < size:
+ timeout_count = 0
+ data = self.tn.read_eager()
+ if len(data):
+ self.fifo.extend(data)
+ timeout_count = 0
+ else:
+ time.sleep(0.25)
+ if self.read_timeout is not None and timeout_count > 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 0000000..2a426f3
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/__init__.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_compat.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/_compat.cpython-39.pyc
new file mode 100644
index 0000000..8c05f78
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_compat.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-39.pyc
new file mode 100644
index 0000000..5d4d292
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-39.pyc
new file mode 100644
index 0000000..683e962
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-39.pyc differ
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 0000000..f915a2c
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/core.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/core.cpython-39.pyc
new file mode 100644
index 0000000..1fb2b76
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/core.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/decorators.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/decorators.cpython-39.pyc
new file mode 100644
index 0000000..864042e
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/decorators.cpython-39.pyc differ
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 0000000..e634c6c
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/exceptions.cpython-39.pyc differ
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 0000000..a0b0003
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/formatting.cpython-39.pyc differ
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 0000000..7491927
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/globals.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/parser.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/parser.cpython-39.pyc
new file mode 100644
index 0000000..c59eb4b
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/parser.cpython-39.pyc differ
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 0000000..c0a1513
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-39.pyc differ
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 0000000..fc6dbbe
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/termui.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/testing.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/testing.cpython-39.pyc
new file mode 100644
index 0000000..76b59d4
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/testing.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/types.cpython-39.pyc b/venv/Lib/site-packages/click/__pycache__/types.cpython-39.pyc
new file mode 100644
index 0000000..1277c44
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/types.cpython-39.pyc differ
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 0000000..6d35c04
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/utils.cpython-39.pyc differ
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 0000000..f649dfd
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-39.pyc b/venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-39.pyc
new file mode 100644
index 0000000..fe81ed2
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-39.pyc differ
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 0000000..9db0a55
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-39.pyc differ
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 0000000..0efe42a
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-39.pyc differ
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 0000000..df52efe
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/win32.cpython-39.pyc differ
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 0000000..f58be64
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-39.pyc differ
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 0000000..74b37f0
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-39.pyc differ
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 0000000..1980be3
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-39.pyc differ
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 0000000..9aaca74
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-39.pyc differ
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 0000000..c38b3df
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-39.pyc differ
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 0000000..f30a0a2
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-39.pyc differ
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 0000000..fce3e33
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-39.pyc differ
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 0000000..74fd2e0
Binary files /dev/null and b/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/cli.py b/venv/Lib/site-packages/dotenv/cli.py
new file mode 100644
index 0000000..b7ae24a
--- /dev/null
+++ b/venv/Lib/site-packages/dotenv/cli.py
@@ -0,0 +1,164 @@
+import os
+import sys
+from subprocess import Popen
+from typing import Any, Dict, List
+
+try:
+ import click
+except ImportError:
+ sys.stderr.write('It seems python-dotenv is not installed with cli option. \n'
+ 'Run pip install "python-dotenv[cli]" to fix this.')
+ sys.exit(1)
+
+from .main import dotenv_values, get_key, set_key, unset_key
+from .version import __version__
+
+
+@click.group()
+@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'),
+ type=click.Path(file_okay=True),
+ help="Location of the .env file, defaults to .env file in current working directory.")
+@click.option('-q', '--quote', default='always',
+ type=click.Choice(['always', 'never', 'auto']),
+ help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.")
+@click.option('-e', '--export', default=False,
+ type=click.BOOL,
+ help="Whether to write the dot file as an executable bash script.")
+@click.version_option(version=__version__)
+@click.pass_context
+def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> 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 0000000..9f353f9
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/__init__.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/__pycache__/__main__.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/__main__.cpython-39.pyc
new file mode 100644
index 0000000..f017206
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/__main__.cpython-39.pyc differ
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 0000000..fcacff0
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/rfc2217.cpython-39.pyc differ
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 0000000..307cf24
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/rs485.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/__pycache__/serialcli.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/serialcli.cpython-39.pyc
new file mode 100644
index 0000000..5cb5870
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/serialcli.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/__pycache__/serialjava.cpython-39.pyc b/venv/Lib/site-packages/serial/__pycache__/serialjava.cpython-39.pyc
new file mode 100644
index 0000000..5526388
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/serialjava.cpython-39.pyc differ
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 0000000..2f2bcf7
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/serialposix.cpython-39.pyc differ
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 0000000..b5e92a3
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/serialutil.cpython-39.pyc differ
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 0000000..3c90152
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/serialwin32.cpython-39.pyc differ
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 0000000..5c1aef4
Binary files /dev/null and b/venv/Lib/site-packages/serial/__pycache__/win32.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/rfc2217.py b/venv/Lib/site-packages/serial/rfc2217.py
new file mode 100644
index 0000000..2ae188e
--- /dev/null
+++ b/venv/Lib/site-packages/serial/rfc2217.py
@@ -0,0 +1,1351 @@
+#! python
+#
+# This module implements a RFC2217 compatible client. RF2217 descibes a
+# protocol to access serial ports over TCP/IP and allows setting the baud rate,
+# modem control lines etc.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2001-2015 Chris Liechti
+#
+# 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 0000000..d1687db
Binary files /dev/null and b/venv/Lib/site-packages/serial/threaded/__pycache__/__init__.cpython-39.pyc differ
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 0000000..381cd29
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/__init__.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/hexlify_codec.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/hexlify_codec.cpython-39.pyc
new file mode 100644
index 0000000..02afa79
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/hexlify_codec.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/list_ports.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports.cpython-39.pyc
new file mode 100644
index 0000000..f6e7195
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports.cpython-39.pyc differ
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 0000000..c93401a
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_common.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_linux.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_linux.cpython-39.pyc
new file mode 100644
index 0000000..2c00750
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_linux.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_osx.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_osx.cpython-39.pyc
new file mode 100644
index 0000000..d7bb1e5
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_osx.cpython-39.pyc differ
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 0000000..4f0c066
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_posix.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_windows.cpython-39.pyc b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_windows.cpython-39.pyc
new file mode 100644
index 0000000..0250eea
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/list_ports_windows.cpython-39.pyc differ
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 0000000..4b1977c
Binary files /dev/null and b/venv/Lib/site-packages/serial/tools/__pycache__/miniterm.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/tools/hexlify_codec.py b/venv/Lib/site-packages/serial/tools/hexlify_codec.py
new file mode 100644
index 0000000..bd8f6b0
--- /dev/null
+++ b/venv/Lib/site-packages/serial/tools/hexlify_codec.py
@@ -0,0 +1,126 @@
+#! python
+#
+# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2015-2016 Chris Liechti
+#
+# 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 0000000..74817b9
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/__init__.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_alt.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_alt.cpython-39.pyc
new file mode 100644
index 0000000..42fa808
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_alt.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_cp2110.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_cp2110.cpython-39.pyc
new file mode 100644
index 0000000..b5d5bd2
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_cp2110.cpython-39.pyc differ
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 0000000..ef668e0
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_hwgrep.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_loop.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_loop.cpython-39.pyc
new file mode 100644
index 0000000..3a8eeab
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_loop.cpython-39.pyc differ
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 0000000..70f870e
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_rfc2217.cpython-39.pyc differ
diff --git a/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_socket.cpython-39.pyc b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_socket.cpython-39.pyc
new file mode 100644
index 0000000..47a2f60
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_socket.cpython-39.pyc differ
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 0000000..7b5b1ec
Binary files /dev/null and b/venv/Lib/site-packages/serial/urlhandler/__pycache__/protocol_spy.cpython-39.pyc differ
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://&