added in micropython external library for const feature.
it's returing valid attribute arrays, that just need converted on z2m side. it's also respecting the stop command from the console, which I will attempt to expose as just a button press.
This commit is contained in:
6
.idea/garageDoorZigbee.iml
generated
6
.idea/garageDoorZigbee.iml
generated
@@ -4,6 +4,11 @@
|
|||||||
<facet type="MicroPython" name="XBee MicroPython">
|
<facet type="MicroPython" name="XBee MicroPython">
|
||||||
<configuration />
|
<configuration />
|
||||||
</facet>
|
</facet>
|
||||||
|
<facet type="MicroPython" name="MicroPython">
|
||||||
|
<configuration>
|
||||||
|
<device name="Pyboard" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
</component>
|
</component>
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
@@ -13,5 +18,6 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="XBee MicroPython" level="project" />
|
<orderEntry type="library" name="XBee MicroPython" level="project" />
|
||||||
|
<orderEntry type="library" name="MicroPython" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
11
.idea/libraries/MicroPython.xml
generated
Normal file
11
.idea/libraries/MicroPython.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="MicroPython" type="python">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://$APPLICATION_PLUGINS_DIR$/intellij-micropython/typehints/stdlib" />
|
||||||
|
<root url="file://$APPLICATION_PLUGINS_DIR$/intellij-micropython/typehints/micropython" />
|
||||||
|
<root url="file://$APPLICATION_PLUGINS_DIR$/intellij-micropython/typehints/pyboard" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
19
barrier.py
19
barrier.py
@@ -3,7 +3,7 @@ import time
|
|||||||
import com
|
import com
|
||||||
import xbee
|
import xbee
|
||||||
from machine import Pin
|
from machine import Pin
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
class Barrier:
|
class Barrier:
|
||||||
moving = b'\x00'
|
moving = b'\x00'
|
||||||
@@ -14,6 +14,10 @@ class Barrier:
|
|||||||
door = ad0.value().to_bytes(2,"big")
|
door = ad0.value().to_bytes(2,"big")
|
||||||
motor = ad1.value().to_bytes(2, "big")
|
motor = ad1.value().to_bytes(2, "big")
|
||||||
update = True
|
update = True
|
||||||
|
duint8 = const(0x20)
|
||||||
|
_duint16 = const(0x21)
|
||||||
|
denum8 = const(0x30)
|
||||||
|
_dbool = const(0x10)
|
||||||
|
|
||||||
def status(self,seq, kwargs):
|
def status(self,seq, kwargs):
|
||||||
#moving state is x0001 enum8(0x30)
|
#moving state is x0001 enum8(0x30)
|
||||||
@@ -21,21 +25,14 @@ class Barrier:
|
|||||||
# 2 octets attribute identifier
|
# 2 octets attribute identifier
|
||||||
# 1 octet attribute data type
|
# 1 octet attribute data type
|
||||||
# 1 octet attribute value
|
# 1 octet attribute value
|
||||||
print("really messed up early in the game eh")
|
|
||||||
attributes = kwargs['attributes']
|
attributes = kwargs['attributes']
|
||||||
print(attributes)
|
|
||||||
if len(attributes) == 1:
|
if len(attributes) == 1:
|
||||||
print("length")
|
|
||||||
if attributes[0] == 10:
|
if attributes[0] == 10:
|
||||||
print("position request")
|
return bytes([0,10]) + bytes([self.duint8]) + self.barrier_position
|
||||||
print(self.barrier_position)
|
|
||||||
return self.barrier_position
|
|
||||||
if attributes[0] == 1:
|
if attributes[0] == 1:
|
||||||
print("moving request")
|
return bytes([0,0]) + bytes([self.denum8]) + self.moving
|
||||||
print(self.moving)
|
|
||||||
return self.moving
|
|
||||||
#to_return = bytes([1,0,48]) + bytes(self.moving) + bytes([10,0,20]) + bytes(self.barrier_position)
|
#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):
|
def command(self, seq, payload):
|
||||||
|
|||||||
47
main.py
47
main.py
@@ -4,17 +4,16 @@
|
|||||||
import time
|
import time
|
||||||
import xbee
|
import xbee
|
||||||
import spec
|
import spec
|
||||||
import ubinascii
|
|
||||||
from machine import I2C
|
|
||||||
import barrier
|
import barrier
|
||||||
from machine import Pin
|
from machine import Pin
|
||||||
import gen
|
import gen
|
||||||
import com
|
import com
|
||||||
|
from micropython import const
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
#ad0 = Pin("D0", Pin.IN, Pin.PULL_UP)
|
#ad0 = Pin("D0", Pin.IN, Pin.PULL_UP)
|
||||||
#ad1 = Pin("D1", 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:
|
while xbee.atcmd("AI") != 0:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
ad4 = Pin("D4", Pin.OUT, value=0)
|
ad4 = Pin("D4", Pin.OUT, value=0)
|
||||||
@@ -25,6 +24,7 @@ xbee.modem_status.callback(status_cb)
|
|||||||
# arduino_addr = 0x48
|
# arduino_addr = 0x48
|
||||||
# senddata = 0
|
# senddata = 0
|
||||||
key = b'\x5A\x69\x67\x42\x65\x65\x41\x6C\x6C\x69\x61\x6E\x63\x65\x30\x39'
|
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)
|
xbee.atcmd('KY', key)
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +40,17 @@ diff = 3600000
|
|||||||
first_report = False
|
first_report = False
|
||||||
timestamp = time.ticks_ms()
|
timestamp = time.ticks_ms()
|
||||||
garage = barrier.Barrier()
|
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:
|
while 1 != 0:
|
||||||
packet = com.receive()
|
packet = com.receive()
|
||||||
if packet is not None:
|
if packet is not None:
|
||||||
@@ -50,17 +60,19 @@ while 1 != 0:
|
|||||||
if packet['cluster'] == 259: #barrier cluster
|
if packet['cluster'] == 259: #barrier cluster
|
||||||
cluster_name, seq, CommandType, command_name, disable_default_response, kwargs = spec.decode_zcl(
|
cluster_name, seq, CommandType, command_name, disable_default_response, kwargs = spec.decode_zcl(
|
||||||
packet['cluster'], packet['payload'])
|
packet['cluster'], packet['payload'])
|
||||||
|
print("printing kwargs for incoming packet")
|
||||||
|
print(command_name)
|
||||||
if "attributes" in kwargs:
|
if "attributes" in kwargs:
|
||||||
#garage.status(seq,packet['payload'])
|
#garage.status(seq,packet['payload'])
|
||||||
print("found attribute request")
|
print("found attribute request")
|
||||||
stat=garage.status(seq, kwargs)
|
stat=garage.status(seq, kwargs)
|
||||||
if payload != -1:
|
if stat != 'b\xffff':
|
||||||
print("garage status")
|
print("garage status")
|
||||||
|
payload = payload_header+ bytes([seq])+ bytes([_rap]) + stat
|
||||||
print(payload)
|
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'])
|
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'])
|
garage.command(seq, packet['payload'])
|
||||||
pass
|
pass
|
||||||
if packet['cluster'] == 6: #genOnOffCluster in HA Profile
|
if packet['cluster'] == 6: #genOnOffCluster in HA Profile
|
||||||
@@ -72,17 +84,14 @@ while 1 != 0:
|
|||||||
# print(kwargs)
|
# print(kwargs)
|
||||||
if "attributes" in kwargs:
|
if "attributes" in kwargs:
|
||||||
if kwargs['attributes'][0] == 0:
|
if kwargs['attributes'][0] == 0:
|
||||||
payload = bytes([12, 30, 16, seq, 1]) #zcl_header
|
payload = payload_header + bytes([seq]) + bytes([_rap]) + bytes([0,0])+ SUCCESS + dbool + bytes([ad4.value()])
|
||||||
payload = payload + bytes([0, 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'],
|
com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'],
|
||||||
cluster=packet['cluster'], profile=packet['profile'])
|
cluster=packet['cluster'], profile=packet['profile'])
|
||||||
if kwargs['attributes'][0] == 10:
|
if kwargs['attributes'][0] == 10:
|
||||||
payload = bytes([12, 30, 16, seq, 1])
|
payload = payload_header + bytes([seq]) + _rap + bytes([0, 0, 16, ad4.value()])
|
||||||
payload = payload + bytes([0, 0, 16, ad4.value()])
|
|
||||||
# payload= attr_bytes
|
# payload= attr_bytes
|
||||||
#print(payload)
|
print(payload)
|
||||||
com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'],
|
com.fancy_transmit(payload=payload, source_ep=packet['dest_ep'], dest_ep=packet['source_ep'],
|
||||||
cluster=packet['cluster'], profile=packet['profile'])
|
cluster=packet['cluster'], profile=packet['profile'])
|
||||||
if command_name == "on":
|
if command_name == "on":
|
||||||
@@ -122,8 +131,8 @@ while 1 != 0:
|
|||||||
attr_bytes=gen.attribute_result(kwargs)
|
attr_bytes=gen.attribute_result(kwargs)
|
||||||
#payload: control byte, code bytes(2), seq copy, command identifier(read_attributes_response,
|
#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([4, 30, 16, seq, 1, attr_bytes, 0, 8, 0])
|
||||||
payload = bytes([12, 30, 16, seq, 1])
|
zcl_header = payload_header + bytes([seq]) + _rap
|
||||||
payload = payload+attr_bytes
|
payload = zcl_header+attr_bytes
|
||||||
#payload= attr_bytes
|
#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'])
|
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
|
#payload = zcl_head# + payload
|
||||||
garage.watch()
|
garage.watch()
|
||||||
florp = garage.barrier_position
|
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=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():
|
if garage.watch():
|
||||||
zcl_head = bytes([12, 30, 16, 171, 10]) # zcl_header
|
zcl_head = bytes([12, 30, 16, 171, 10]) # zcl_header
|
||||||
payl = zcl_head + garage.status()
|
payl = zcl_head + garage.status()
|
||||||
|
|||||||
BIN
venv/Lib/site-packages/__pycache__/docopt.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/__pycache__/docopt.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/__pycache__/uflash.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/__pycache__/uflash.cpython-39.pyc
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
21
venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/LICENSE
Normal file
21
venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/LICENSE
Normal file
@@ -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.
|
||||||
147
venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/METADATA
Normal file
147
venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/METADATA
Normal file
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
17
venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/RECORD
Normal file
17
venv/Lib/site-packages/adafruit_ampy-1.0.7.dist-info/RECORD
Normal file
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[console_scripts]
|
||||||
|
ampy = ampy.cli:cli
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ampy
|
||||||
0
venv/Lib/site-packages/ampy/__init__.py
Normal file
0
venv/Lib/site-packages/ampy/__init__.py
Normal file
BIN
venv/Lib/site-packages/ampy/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/ampy/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/ampy/__pycache__/cli.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/ampy/__pycache__/cli.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/ampy/__pycache__/files.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/ampy/__pycache__/files.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/ampy/__pycache__/pyboard.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/ampy/__pycache__/pyboard.cpython-39.pyc
Normal file
Binary file not shown.
429
venv/Lib/site-packages/ampy/cli.py
Normal file
429
venv/Lib/site-packages/ampy/cli.py
Normal file
@@ -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
|
||||||
310
venv/Lib/site-packages/ampy/files.py
Normal file
310
venv/Lib/site-packages/ampy/files.py
Normal file
@@ -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
|
||||||
343
venv/Lib/site-packages/ampy/pyboard.py
Normal file
343
venv/Lib/site-packages/ampy/pyboard.py
Normal file
@@ -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()
|
||||||
1
venv/Lib/site-packages/click-8.1.2.dist-info/INSTALLER
Normal file
1
venv/Lib/site-packages/click-8.1.2.dist-info/INSTALLER
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
28
venv/Lib/site-packages/click-8.1.2.dist-info/LICENSE.rst
Normal file
28
venv/Lib/site-packages/click-8.1.2.dist-info/LICENSE.rst
Normal file
@@ -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.
|
||||||
111
venv/Lib/site-packages/click-8.1.2.dist-info/METADATA
Normal file
111
venv/Lib/site-packages/click-8.1.2.dist-info/METADATA
Normal file
@@ -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
|
||||||
|
|
||||||
|
|
||||||
39
venv/Lib/site-packages/click-8.1.2.dist-info/RECORD
Normal file
39
venv/Lib/site-packages/click-8.1.2.dist-info/RECORD
Normal file
@@ -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
|
||||||
5
venv/Lib/site-packages/click-8.1.2.dist-info/WHEEL
Normal file
5
venv/Lib/site-packages/click-8.1.2.dist-info/WHEEL
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.37.1)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
click
|
||||||
73
venv/Lib/site-packages/click/__init__.py
Normal file
73
venv/Lib/site-packages/click/__init__.py
Normal file
@@ -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"
|
||||||
BIN
venv/Lib/site-packages/click/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/_compat.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/_compat.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/core.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/core.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/globals.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/globals.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/parser.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/parser.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/termui.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/termui.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/testing.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/testing.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/types.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/types.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/click/__pycache__/utils.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/click/__pycache__/utils.cpython-39.pyc
Normal file
Binary file not shown.
626
venv/Lib/site-packages/click/_compat.py
Normal file
626
venv/Lib/site-packages/click/_compat.py
Normal file
@@ -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,
|
||||||
|
}
|
||||||
717
venv/Lib/site-packages/click/_termui_impl.py
Normal file
717
venv/Lib/site-packages/click/_termui_impl.py
Normal file
@@ -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
|
||||||
49
venv/Lib/site-packages/click/_textwrap.py
Normal file
49
venv/Lib/site-packages/click/_textwrap.py
Normal file
@@ -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)
|
||||||
279
venv/Lib/site-packages/click/_winconsole.py
Normal file
279
venv/Lib/site-packages/click/_winconsole.py
Normal file
@@ -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"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
2995
venv/Lib/site-packages/click/core.py
Normal file
2995
venv/Lib/site-packages/click/core.py
Normal file
File diff suppressed because it is too large
Load Diff
497
venv/Lib/site-packages/click/decorators.py
Normal file
497
venv/Lib/site-packages/click/decorators.py
Normal file
@@ -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)
|
||||||
287
venv/Lib/site-packages/click/exceptions.py
Normal file
287
venv/Lib/site-packages/click/exceptions.py
Normal file
@@ -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
|
||||||
301
venv/Lib/site-packages/click/formatting.py
Normal file
301
venv/Lib/site-packages/click/formatting.py
Normal file
@@ -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
|
||||||
68
venv/Lib/site-packages/click/globals.py
Normal file
68
venv/Lib/site-packages/click/globals.py
Normal file
@@ -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
|
||||||
529
venv/Lib/site-packages/click/parser.py
Normal file
529
venv/Lib/site-packages/click/parser.py
Normal file
@@ -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)
|
||||||
0
venv/Lib/site-packages/click/py.typed
Normal file
0
venv/Lib/site-packages/click/py.typed
Normal file
580
venv/Lib/site-packages/click/shell_completion.py
Normal file
580
venv/Lib/site-packages/click/shell_completion.py
Normal file
@@ -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
|
||||||
787
venv/Lib/site-packages/click/termui.py
Normal file
787
venv/Lib/site-packages/click/termui.py
Normal file
@@ -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)
|
||||||
479
venv/Lib/site-packages/click/testing.py
Normal file
479
venv/Lib/site-packages/click/testing.py
Normal file
@@ -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="<stdin>", mode="r"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.echo_stdin:
|
||||||
|
# Force unbuffered reads, otherwise TextIOWrapper reads a
|
||||||
|
# large chunk which is echoed early.
|
||||||
|
text_input._CHUNK_SIZE = 1 # type: ignore
|
||||||
|
|
||||||
|
sys.stdout = _NamedTextIOWrapper(
|
||||||
|
bytes_output, encoding=self.charset, name="<stdout>", mode="w"
|
||||||
|
)
|
||||||
|
|
||||||
|
bytes_error = None
|
||||||
|
if self.mix_stderr:
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
else:
|
||||||
|
bytes_error = io.BytesIO()
|
||||||
|
sys.stderr = _NamedTextIOWrapper(
|
||||||
|
bytes_error,
|
||||||
|
encoding=self.charset,
|
||||||
|
name="<stderr>",
|
||||||
|
mode="w",
|
||||||
|
errors="backslashreplace",
|
||||||
|
)
|
||||||
|
|
||||||
|
@_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
|
||||||
1073
venv/Lib/site-packages/click/types.py
Normal file
1073
venv/Lib/site-packages/click/types.py
Normal file
File diff suppressed because it is too large
Load Diff
580
venv/Lib/site-packages/click/utils.py
Normal file
580
venv/Lib/site-packages/click/utils.py
Normal file
@@ -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"<unopened file '{self.name}' {self.mode}>"
|
||||||
|
|
||||||
|
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\<user>\AppData\Roaming\Foo Bar``
|
||||||
|
Windows (not roaming):
|
||||||
|
``C:\Users\<user>\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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
27
venv/Lib/site-packages/colorama-0.4.4.dist-info/LICENSE.txt
Normal file
27
venv/Lib/site-packages/colorama-0.4.4.dist-info/LICENSE.txt
Normal file
@@ -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.
|
||||||
415
venv/Lib/site-packages/colorama-0.4.4.dist-info/METADATA
Normal file
415
venv/Lib/site-packages/colorama-0.4.4.dist-info/METADATA
Normal file
@@ -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 <https://pypi.org/project/colorama/>`_ ·
|
||||||
|
`Github for source <https://github.com/tartley/colorama>`_ ·
|
||||||
|
`Colorama for enterprise on Tidelift <https://github.com/tartley/colorama/blob/master/ENTERPRISE.md>`_
|
||||||
|
|
||||||
|
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 <https://pypi.org/project/termcolor/>`_
|
||||||
|
or the fabulous `Blessings <https://pypi.org/project/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 [ <param> ; <param> ... <command>
|
||||||
|
|
||||||
|
Where ``<param>`` is an integer, and ``<command>`` is a single letter. Zero or
|
||||||
|
more params are passed to a ``<command>``. 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 [ <param> ; <param> ... <command>``
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
18
venv/Lib/site-packages/colorama-0.4.4.dist-info/RECORD
Normal file
18
venv/Lib/site-packages/colorama-0.4.4.dist-info/RECORD
Normal file
@@ -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
|
||||||
6
venv/Lib/site-packages/colorama-0.4.4.dist-info/WHEEL
Normal file
6
venv/Lib/site-packages/colorama-0.4.4.dist-info/WHEEL
Normal file
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
colorama
|
||||||
6
venv/Lib/site-packages/colorama/__init__.py
Normal file
6
venv/Lib/site-packages/colorama/__init__.py
Normal file
@@ -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'
|
||||||
Binary file not shown.
BIN
venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/colorama/__pycache__/win32.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/colorama/__pycache__/win32.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
102
venv/Lib/site-packages/colorama/ansi.py
Normal file
102
venv/Lib/site-packages/colorama/ansi.py
Normal file
@@ -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()
|
||||||
258
venv/Lib/site-packages/colorama/ansitowin32.py
Normal file
258
venv/Lib/site-packages/colorama/ansitowin32.py
Normal file
@@ -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
|
||||||
80
venv/Lib/site-packages/colorama/initialise.py
Normal file
80
venv/Lib/site-packages/colorama/initialise.py
Normal file
@@ -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
|
||||||
152
venv/Lib/site-packages/colorama/win32.py
Normal file
152
venv/Lib/site-packages/colorama/win32.py
Normal file
@@ -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)
|
||||||
169
venv/Lib/site-packages/colorama/winterm.py
Normal file
169
venv/Lib/site-packages/colorama/winterm.py
Normal file
@@ -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)
|
||||||
1
venv/Lib/site-packages/docopt-0.6.2.dist-info/INSTALLER
Normal file
1
venv/Lib/site-packages/docopt-0.6.2.dist-info/INSTALLER
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
19
venv/Lib/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT
Normal file
19
venv/Lib/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2012 Vladimir Keleshev, <vladimir@keleshev.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
470
venv/Lib/site-packages/docopt-0.6.2.dist-info/METADATA
Normal file
470
venv/Lib/site-packages/docopt-0.6.2.dist-info/METADATA
Normal file
@@ -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 <http://youtu.be/pXhcPJK5cMc>`_
|
||||||
|
|
||||||
|
New in version 0.6.1:
|
||||||
|
|
||||||
|
- Fix issue `#85 <https://github.com/docopt/docopt/issues/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 <name>...
|
||||||
|
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
|
||||||
|
naval_fate.py ship shoot <x> <y>
|
||||||
|
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
|
||||||
|
naval_fate.py (-h | --help)
|
||||||
|
naval_fate.py --version
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help Show this screen.
|
||||||
|
--version Show version.
|
||||||
|
--speed=<kn> 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 <http://www.python.org/dev/peps/pep-0257/>`_ recommends
|
||||||
|
putting help message in the module docstrings.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Use `pip <http://pip-installer.org>`_ 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,
|
||||||
|
'<name>': ['Guardian'], 'ship': True,
|
||||||
|
'<x>': '100', 'shoot': False,
|
||||||
|
'<y>': '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**. Arguments are specified as either
|
||||||
|
upper-case words, e.g. ``my_program.py CONTENT-PATH`` or words
|
||||||
|
surrounded by angular brackets: ``my_program.py <content-path>``.
|
||||||
|
- **--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 ``<arguments>`` 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=<path> <file>...`` is the same as ``my_program.py
|
||||||
|
(--path=<path> <file>...)``. (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 <file> <file> --path=<path>...
|
||||||
|
|
||||||
|
I.e. invoked with ``my_program.py file1 file2 --path=./here
|
||||||
|
--path=./there`` the returned dict will contain ``args['<file>'] ==
|
||||||
|
['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 <angular-brackets> 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 <file>, --input <file> # 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:
|
||||||
|
<my-default-value>]``::
|
||||||
|
|
||||||
|
--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=<arg> --repeatable=<arg>]
|
||||||
|
[--another-repeatable=<arg>]...
|
||||||
|
[--not-repeatable=<arg>]
|
||||||
|
|
||||||
|
# will be ['./here', './there']
|
||||||
|
--repeatable=<arg> [default: ./here ./there]
|
||||||
|
|
||||||
|
# will be ['./here']
|
||||||
|
--another-repeatable=<arg> [default: ./here]
|
||||||
|
|
||||||
|
# will be './here ./there', because it is not repeatable
|
||||||
|
--not-repeatable=<arg> [default: ./here ./there]
|
||||||
|
|
||||||
|
Examples
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
We have an extensive list of `examples
|
||||||
|
<https://github.com/docopt/docopt/tree/master/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
|
||||||
|
<https://github.com/docopt/docopt/tree/master/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
|
||||||
|
<https://github.com/halst/schema>`_ which make validating data a
|
||||||
|
breeze. Take a look at `validation_example.py
|
||||||
|
<https://github.com/docopt/docopt/tree/master/examples/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 <http://github.com/docopt/docopt/issues>`_
|
||||||
|
|
||||||
|
Make pull requrests, report bugs, suggest ideas and discuss
|
||||||
|
**docopt**. You can also drop a line directly to
|
||||||
|
<vladimir@keleshev.com>.
|
||||||
|
|
||||||
|
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 <http://github.com/docopt/docopt.rb>`_
|
||||||
|
- `CoffeeScript port <http://github.com/docopt/docopt.coffee>`_
|
||||||
|
- `Lua port <http://github.com/docopt/docopt.lua>`_
|
||||||
|
- `PHP port <http://github.com/docopt/docopt.php>`_
|
||||||
|
|
||||||
|
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
|
||||||
|
<http://github.com/docopt/docopt>`_.
|
||||||
|
|
||||||
|
Porting discussion is on `issues page
|
||||||
|
<http://github.com/docopt/docopt/issues>`_.
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
**docopt** follows `semantic versioning <http://semver.org>`_. 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 <http://pythonwheels.com/>`_ 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).
|
||||||
|
|
||||||
|
|
||||||
9
venv/Lib/site-packages/docopt-0.6.2.dist-info/RECORD
Normal file
9
venv/Lib/site-packages/docopt-0.6.2.dist-info/RECORD
Normal file
@@ -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
|
||||||
6
venv/Lib/site-packages/docopt-0.6.2.dist-info/WHEEL
Normal file
6
venv/Lib/site-packages/docopt-0.6.2.dist-info/WHEEL
Normal file
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
docopt
|
||||||
579
venv/Lib/site-packages/docopt.py
Normal file
579
venv/Lib/site-packages/docopt.py
Normal file
@@ -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, <positional-argument>, 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 "<path>", and values are the
|
||||||
|
parsed values of those elements.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
>>> from docopt import docopt
|
||||||
|
>>> doc = '''
|
||||||
|
Usage:
|
||||||
|
my_program tcp <host> <port> [--timeout=<seconds>]
|
||||||
|
my_program serial <port> [--baud=<n>] [--timeout=<seconds>]
|
||||||
|
my_program (-h | --help | --version)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show this screen and exit.
|
||||||
|
--baud=<n> Baudrate [default: 9600]
|
||||||
|
'''
|
||||||
|
>>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30']
|
||||||
|
>>> docopt(doc, argv)
|
||||||
|
{'--baud': '9600',
|
||||||
|
'--help': False,
|
||||||
|
'--timeout': '30',
|
||||||
|
'--version': False,
|
||||||
|
'<host>': '127.0.0.1',
|
||||||
|
'<port>': '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()
|
||||||
49
venv/Lib/site-packages/dotenv/__init__.py
Normal file
49
venv/Lib/site-packages/dotenv/__init__.py
Normal file
@@ -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']
|
||||||
Binary file not shown.
BIN
venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/dotenv/__pycache__/main.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/dotenv/__pycache__/main.cpython-39.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/dotenv/__pycache__/version.cpython-39.pyc
Normal file
BIN
venv/Lib/site-packages/dotenv/__pycache__/version.cpython-39.pyc
Normal file
Binary file not shown.
164
venv/Lib/site-packages/dotenv/cli.py
Normal file
164
venv/Lib/site-packages/dotenv/cli.py
Normal file
@@ -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()
|
||||||
39
venv/Lib/site-packages/dotenv/ipython.py
Normal file
39
venv/Lib/site-packages/dotenv/ipython.py
Normal file
@@ -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)
|
||||||
373
venv/Lib/site-packages/dotenv/main.py
Normal file
373
venv/Lib/site-packages/dotenv/main.py
Normal file
@@ -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()
|
||||||
182
venv/Lib/site-packages/dotenv/parser.py
Normal file
182
venv/Lib/site-packages/dotenv/parser.py
Normal file
@@ -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)
|
||||||
1
venv/Lib/site-packages/dotenv/py.typed
Normal file
1
venv/Lib/site-packages/dotenv/py.typed
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Marker file for PEP 561
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user